Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding
From: sashiko-bot @ 2026-06-12 19:57 UTC (permalink / raw)
  To: Atanas Filipov; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260612194417.1737009-2-atanas.filipov@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Schema validation weakness and missing required properties cause driver probe failure.
- [Medium] Chronologically backward compatible fallback with mismatched hardware constraints.
- [Low] The binding introduces the deprecated `cell-index` property.
--

commit 8d581d21294da3512a77773ccfed01692993fa7b
Author: Atanas Filipov <atanas.filipov@oss.qualcomm.com>

dt-bindings: media: qcom: Add JPEG encoder binding

Add device-tree binding for the standalone Qualcomm JPEG encoder hardware
block (separate from CAMSS media pipelines). Document required resources
briefly and scope initial support to currently used compatibles in this
series, including SM8250, QCM6490, and SM8550 class platforms.

> diff --git a/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
> new file mode 100644
> index 0000000000000..83f19719a8693
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
> @@ -0,0 +1,142 @@

[ ... ]

> +properties:
> +  compatible:
> +    oneOf:
> +      - items:
> +          - const: qcom,sm8250-jenc
> +      - items:
> +          - enum:
> +              - qcom,qcm6490-jenc
> +          - const: qcom,sm8550-jenc

[Severity: Medium]
Is it safe to use qcom,sm8550-jenc as a fallback for qcom,qcm6490-jenc?

It looks like the driver uses different performance and clock configuration
data for these two SoCs (qcom_t680_jpeg_drvdata vs qcom_t780_jpeg_drvdata
in drivers/media/platform/qcom/jpeg/qcom_jenc_res.c). 

If a generic driver relies on this fallback, it would apply SM8550 clock
frequencies to QCM6490 hardware, which might cause instability. Also, newer
hardware falling back to older hardware is usually the standard devicetree
convention.

> +      - items:
> +          - const: qcom,sm8550-jenc
> +
> +  cell-index:
> +    description: Legacy JPEG HW instance index.
> +    $ref: /schemas/types.yaml#/definitions/uint32

[Severity: Low]
Is this deprecated cell-index property still required?

The introduced Linux driver does not appear to read or use this property.
Could instance identification be handled dynamically or via standardized
devicetree aliases nodes instead?

> +  reg:
> +    maxItems: 1
> +
> +  reg-names:
> +    oneOf:
> +      - items:
> +          - const: jpeg
> +      - items:
> +          - const: jpeg-regs
> +      - items:
> +          - const: jpeg_regs
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    minItems: 6
> +    maxItems: 6
> +
> +  clock-names:
> +    oneOf:
> +      - items:
> +          - const: gcc_hf_axi
> +          - const: gcc_sf_axi
> +          - const: core_ahb
> +          - const: cpas_ahb
> +          - const: camnoc_axi
> +          - const: jpeg
> +      - items:
> +          - const: gcc_hf_axi_clk
> +          - const: gcc_sf_axi_clk
> +          - const: core_ahb_clk
> +          - const: cpas_ahb_clk
> +          - const: camnoc_axi_clk
> +          - const: jpeg_clk

[Severity: High]
Should the binding strictly enforce the exact resource names the driver expects?

The schema allows variations like "jpeg-regs" and "_clk" suffixes, but the
driver probe in qcom_jpeg_probe() strictly calls:

devm_platform_ioremap_resource_byname(pdev, "jpeg")

and expects non-suffix clock names (e.g., "core_ahb"). If a devicetree uses
the alternative schema-approved names, won't the driver probe fail?

[ ... ]

> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - interrupts
> +  - power-domains
> +  - iommus
> +  - interconnects
> +  - interconnect-names

[Severity: High]
Should reg-names and clock-names be added to this required list?

Since the driver strictly relies on these names to find its resources during
probe, omitting them from the devicetree will cause probe failures.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260612194417.1737009-1-atanas.filipov@oss.qualcomm.com?part=1

^ permalink raw reply

* Re: [PATCH v11 2/9] i3c: master: Expose the APIs to support I3C hub
From: Frank Li @ 2026-06-12 19:52 UTC (permalink / raw)
  To: sashiko-reviews
  Cc: Lakshay Piplani, devicetree, Frank.Li, linux-i3c, conor+dt, robh
In-Reply-To: <20260612114421.2BEE31F000E9@smtp.kernel.org>

On Fri, Jun 12, 2026 at 11:44:20AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
>
> New issues:
> - [Medium] API documentation mismatch: i3c_master_reattach_i3c_dev_locked() claims to return -EBUSY on collision, but unconditionally overwrites slot status.
> - [Low] Invalid lock context expressions and typos in newly added IBI kernel-doc.
>
> Pre-existing issues:
> - [Critical] Multiple Use-After-Free vulnerabilities in i3c_dev_free_ibi_locked() due to improper synchronization and teardown order.
> - [High] Unbalanced release of address slots in i3c_master_reattach_i3c_dev_locked() failure path leads to bus corruption.
> --
>
> commit 13a1ff5c64b48ceff03545ae3bebd5e60a136c1e
> Author: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
>
> i3c: master: Expose the APIs to support I3C hub
>
> This patch exposes several internal I3C master functions to allow new I3C
> hub drivers to use them. It adds kernel-doc comments and exports symbols
> like i3c_master_reattach_i3c_dev_locked and various IBI handling functions.
>
> > diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
> > index c45b450af319d..ec3547f75469e 100644
> > --- a/drivers/i3c/master.c
> > +++ b/drivers/i3c/master.c
> > @@ -1652,8 +1652,23 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master,
> >  	return 0;
> >  }
> >
> > -static int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev,
> > -					      u8 old_dyn_addr)
> > +/**
> > + * i3c_master_reattach_i3c_dev_locked() - reattach an I3C device with a new address
> > + * @dev: I3C device descriptor to reattach
> > + * @old_dyn_addr: previous dynamic address of the device
> > + *
> > + * This function reattaches an existing I3C device to the bus when its dynamic
> > + * address has changed. It updates the bus address slot status accordingly:
> > + * - Marks the new dynamic address as occupied by an I3C device.
> > + * - Frees the old dynamic address slot if applicable.
> > + *
> > + * This function must be called with the bus lock held in write mode.
> > + *
> > + * Return: 0 on success, or a negative error code if reattachment fails
> > + *         (e.g. -EBUSY if the new address slot is not free).
> > + */
> > +int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev,
> > +				       u8 old_dyn_addr)
> >  {
>
> [Severity: Medium]
> The kernel-doc states this returns -EBUSY if the new address slot is not free,
> but does the implementation actually check this?
>
> Looking at the rest of i3c_master_reattach_i3c_dev_locked(), it appears
> i3c_bus_set_addr_slot_status() is called unconditionally to set
> I3C_ADDR_SLOT_I3C_DEV without first checking if the slot is available.
>
> Could this silently overwrite an existing occupied slot if an external driver
> relies on the documented failure behavior?
>
> [Severity: High]
> This is a pre-existing issue, but does the error path safely handle a failure
> from master->ops->reattach_i3c_dev()?
>
> When the reattach operation fails, i3c_master_put_i3c_addrs(dev) is called,
> which marks the address slots as free. However, it doesn't appear to revert
> the hardware device's assigned address.
>
> Since callers like i3c_master_add_i3c_dev_locked() completely ignore the
> return value of this function, could this leave the bus manager out of sync
> with the hardware state and cause severe address collisions if the slot is
> subsequently reassigned?
>
> [ ... ]
>
> > @@ -3195,6 +3211,16 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
> >  	return master->ops->i3c_xfers(dev, xfers, nxfers, mode);
> >  }
> >
> > +/**
> > + * i3c_dev_disable_ibi_locked() - Disable IBIs coming from a specific device
> > + * @dev: device on which IBIs should be disabled
> > + *
> > + * This function disable IBIs coming from a specific device and wait for
> > + * all pending IBIs to be processed.
> > + *
> > + * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held.
> > + * Return: 0 in case of success, a negative error core otherwise.
> > + */
>
> [Severity: Low]
> Is the lock context expression correct here?
>
> The parameter is of type struct i3c_dev_desc, which does not have a desc
> member, so &dev->desc->ibi_lock appears to be structurally invalid.
> Should this be &dev->ibi_lock instead?

Make sense, please fix it

>
> Also, there is a minor typo: "error core" should likely be "error code".

Please this typo

Frank
>
> [ ... ]
>
> > +/**
> > + * i3c_dev_free_ibi_locked() - Free all resources needed for IBI handling
> > + * @dev: device on which you want to release IBI resources
> > + *
> > + * This function is responsible for de-allocating resources previously
> > + * allocated by i3c_device_request_ibi(). It should be called after disabling
> > + * IBIs with i3c_device_disable_ibi().
> > + *
> > + * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held.
> > + */
> >  void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
>
> [Severity: Critical]
> This is a pre-existing issue, but does this teardown order introduce a
> use-after-free regression when exposed to external drivers?
>
> Looking at the body of i3c_dev_free_ibi_locked(), the function calls
> master->ops->free_ibi(dev) before destroy_workqueue(dev->ibi->wq).
>
> If free_ibi() frees the underlying IBI pool memory, and destroy_workqueue()
> subsequently flushes pending async work items, could those work items
> execute and dereference the already freed pool?
>
> Additionally, if i3c_dev_disable_ibi_locked() had failed to disable the
> hardware interrupt, could a firing interrupt access dev->ibi after it is
> unconditionally freed by kfree(dev->ibi) at the end of this function?
>
> >  {
> >  	struct i3c_master_controller *master = i3c_dev_get_master(dev);
> > @@ -3301,6 +3366,7 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
> >  	kfree(dev->ibi);
> >  	dev->ibi = NULL;
> >  }
> > +EXPORT_SYMBOL_GPL(i3c_dev_free_ibi_locked);
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260612111816.3688240-1-lakshay.piplani@nxp.com?part=2

^ permalink raw reply

* [PATCH v1 4/4] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
  To: linux-media
  Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel, atanas.filipov
In-Reply-To: <20260612194417.1737009-1-atanas.filipov@oss.qualcomm.com>

Add a Qualcomm JPEG encoder driver implemented on top of the
V4L2 mem2mem framework.

The driver wires vb2 queue handling, format negotiation, JPEG header
handling, interrupt-driven job completion, and runtime PM/clock/ICC
integration for the standalone JPEG encode hardware block.

Initial support in this series targets SM8250, QCM6490, and SM8550
class platforms.

Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
 drivers/media/platform/qcom/Kconfig           |    1 +
 drivers/media/platform/qcom/Makefile          |    1 +
 drivers/media/platform/qcom/jpeg/Kconfig      |   18 +
 drivers/media/platform/qcom/jpeg/Makefile     |    9 +
 .../media/platform/qcom/jpeg/qcom_jenc_defs.h |  244 +++
 .../media/platform/qcom/jpeg/qcom_jenc_dev.c  |  336 ++++
 .../media/platform/qcom/jpeg/qcom_jenc_dev.h  |  107 ++
 .../media/platform/qcom/jpeg/qcom_jenc_hdr.c  |  360 ++++
 .../media/platform/qcom/jpeg/qcom_jenc_hdr.h  |  119 ++
 .../media/platform/qcom/jpeg/qcom_jenc_ops.c  | 1658 +++++++++++++++++
 .../media/platform/qcom/jpeg/qcom_jenc_ops.h  |   52 +
 .../media/platform/qcom/jpeg/qcom_jenc_res.c  |  226 +++
 .../media/platform/qcom/jpeg/qcom_jenc_res.h  |   54 +
 .../qcom/jpeg/qcom_jenc_v420_hw_info.h        |  529 ++++++
 .../media/platform/qcom/jpeg/qcom_jenc_v4l2.c | 1109 +++++++++++
 .../media/platform/qcom/jpeg/qcom_jenc_v4l2.h |   25 +
 16 files changed, 4848 insertions(+)
 create mode 100644 drivers/media/platform/qcom/jpeg/Kconfig
 create mode 100644 drivers/media/platform/qcom/jpeg/Makefile
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_res.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_res.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h

diff --git a/drivers/media/platform/qcom/Kconfig b/drivers/media/platform/qcom/Kconfig
index 4f4d3a68e6e5..f33d53a754a0 100644
--- a/drivers/media/platform/qcom/Kconfig
+++ b/drivers/media/platform/qcom/Kconfig
@@ -5,3 +5,4 @@ comment "Qualcomm media platform drivers"
 source "drivers/media/platform/qcom/camss/Kconfig"
 source "drivers/media/platform/qcom/iris/Kconfig"
 source "drivers/media/platform/qcom/venus/Kconfig"
+source "drivers/media/platform/qcom/jpeg/Kconfig"
diff --git a/drivers/media/platform/qcom/Makefile b/drivers/media/platform/qcom/Makefile
index ea2221a202c0..30c94949e9de 100644
--- a/drivers/media/platform/qcom/Makefile
+++ b/drivers/media/platform/qcom/Makefile
@@ -2,3 +2,4 @@
 obj-y += camss/
 obj-y += iris/
 obj-y += venus/
+obj-y += jpeg/
diff --git a/drivers/media/platform/qcom/jpeg/Kconfig b/drivers/media/platform/qcom/jpeg/Kconfig
new file mode 100644
index 000000000000..6df7edc07229
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/Kconfig
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_QCOM_JENC
+	tristate "Qualcomm V4L2 JPEG Encoder driver"
+	depends on V4L_MEM2MEM_DRIVERS
+	depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST
+	depends on VIDEO_DEV
+	select VIDEO_V4L2_SUBDEV_API
+	select VIDEOBUF2_DMA_SG
+	select V4L2_JPEG_HELPER
+	select V4L2_MEM2MEM_DEV
+	help
+	  Qualcomm JPEG memory-to-memory V4L2 encoder driver.
+
+	  Provides:
+	    - qcom-jenc (encode)
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called qcom-jenc
diff --git a/drivers/media/platform/qcom/jpeg/Makefile b/drivers/media/platform/qcom/jpeg/Makefile
new file mode 100644
index 000000000000..310f6c3c1f19
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_VIDEO_QCOM_JENC) += qcom-jenc.o
+
+qcom-jenc-objs += \
+	qcom_jenc_dev.o \
+	qcom_jenc_v4l2.o \
+	qcom_jenc_ops.o \
+	qcom_jenc_res.o \
+	qcom_jenc_hdr.o
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h
new file mode 100644
index 000000000000..00647a5d06ec
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h
@@ -0,0 +1,244 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_DEFS_H_
+#define QCOM_JENC_DEFS_H_
+
+#include <linux/bitfield.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <media/videobuf2-core.h>
+
+/* Offline JPEG encoder constraints */
+#define QCOM_JPEG_HW_MAX_WIDTH	8192
+#define QCOM_JPEG_HW_MAX_HEIGHT	8192
+#define QCOM_JPEG_HW_MIN_WIDTH	128
+#define QCOM_JPEG_HW_MIN_HEIGHT	96
+
+#define QCOM_JPEG_HW_DEF_HSTEP	16
+#define QCOM_JPEG_HW_DEF_VSTEP	16
+
+#define QCOM_JPEG_HW_DEF_WIDTH	1920
+#define QCOM_JPEG_HW_DEF_HEIGHT	1088
+
+#define QCOM_JPEG_MAX_PLANES	3
+
+#define QCOM_JPEG_QUALITY_MIN	1
+#define QCOM_JPEG_QUALITY_DEF	95
+#define QCOM_JPEG_QUALITY_MAX	100
+#define QCOM_JPEG_QUALITY_MID	(QCOM_JPEG_QUALITY_MAX / 2)
+#define QCOM_JPEG_QUALITY_UNT	1
+
+enum qcom_soc_perf_level {
+	/* Idle/suspend state, not for active encode performance selection. */
+	QCOM_SOC_PERF_SUSPEND = 0,
+	QCOM_SOC_PERF_LOWSVS,
+	QCOM_SOC_PERF_SVS,
+	QCOM_SOC_PERF_SVS_L1,
+	QCOM_SOC_PERF_NOMINAL,
+	QCOM_SOC_PERF_TURBO,
+	QCOM_SOC_PERF_LEVEL_MAX,
+};
+
+enum qcom_jpeg_mask_id {
+	JMSK_HW_VER_STEP,
+	JMSK_HW_VER_MINOR,
+	JMSK_HW_VER_MAJOR,
+
+	JMSK_HW_CAP_ENCODE,
+	JMSK_HW_CAP_DECODE,
+	JMSK_HW_CAP_UPSCALE,
+	JMSK_HW_CAP_DOWNSCALE,
+
+	JMSK_RST_CMD_COMMON,
+	JMSK_RST_CMD_FE_RESET,
+	JMSK_RST_CMD_WE_RESET,
+	JMSK_RST_CMD_ENCODER_RESET,
+	JMSK_RST_CMD_DECODER_RESET,
+	JMSK_RST_CMD_BLOCK_FORMATTER_RST,
+	JMSK_RST_CMD_SCALE_RESET,
+	JMSK_RST_CMD_REGISTER_RESET,
+	JMSK_RST_CMD_MISR_RESET,
+	JMSK_RST_CMD_CORE_RESET,
+	JMSK_RST_CMD_JMSK_DOMAIN_RESET,
+	JMSK_RST_CMD_RESET_BYPASS,
+
+	JMSK_CMD_HW_START,
+	JMSK_CMD_HW_STOP,
+	JMSK_CMD_CLR_RD_PLNS_QUEUE,
+	JMSK_CMD_CLR_WR_PLNS_QUEUE,
+	JMSK_CMD_APPLY_SWC_RD_PARAMS,
+
+	JMSK_CORE_CFG_FE_ENABLE,
+	JMSK_CORE_CFG_WE_ENABLE,
+	JMSK_CORE_CFG_ENC_ENABLE,
+	JMSK_CORE_CFG_SCALE_ENABLE,
+	JMSK_CORE_CFG_TESTBUS_ENABLE,
+	JMSK_CORE_CFG_MODE,
+	JMSK_CORE_CFG_CGC_DISABLE,
+
+	JMSK_CORE_STATUS_ENCODE_STATE,
+	JMSK_CORE_STATUS_SCALE_STATE,
+	JMSK_CORE_STATUS_RT_STATE,
+	JMSK_CORE_STATUS_BUS_STATE,
+	JMSK_CORE_STATUS_CGC_STATE,
+
+	JMSK_IRQ_STATUS_SESSION_DONE,
+	JMSK_IRQ_STATUS_RD_BUF_PLN0_DONE,
+	JMSK_IRQ_STATUS_RD_BUF_PLN1_DONE,
+	JMSK_IRQ_STATUS_RD_BUF_PLN2_DONE,
+	JMSK_IRQ_STATUS_RD_BUF_PLNS_ATTN,
+	JMSK_IRQ_STATUS_WR_BUF_PLN0_DONE,
+	JMSK_IRQ_STATUS_WR_BUF_PLN1_DONE,
+	JMSK_IRQ_STATUS_WR_BUF_PLN2_DONE,
+	JMSK_IRQ_STATUS_WR_BUF_PLNS_ATTN,
+	JMSK_IRQ_STATUS_SESSION_ERROR,
+	JMSK_IRQ_STATUS_STOP_ACK,
+	JMSK_IRQ_STATUS_RESET_ACK,
+
+	JMSK_IRQ_STATUS_ALL_BITS,
+
+	JMSK_FE_CFG_BYTE_ORDERING,
+	JMSK_FE_CFG_BURST_LENGTH_MAX,
+	JMSK_FE_CFG_MEMORY_FORMAT,
+	JMSK_FE_CFG_CBCR_ORDER,
+	JMSK_FE_CFG_BOTTOM_VPAD_EN,
+	JMSK_FE_CFG_PLN0_EN,
+	JMSK_FE_CFG_PLN1_EN,
+	JMSK_FE_CFG_PLN2_EN,
+	JMSK_FE_CFG_SIXTEEN_MCU_EN,
+	JMSK_FE_CFG_MCUS_PER_BLOCK,
+	JMSK_FE_CFG_MAL_BOUNDARY,
+	JMSK_FE_CFG_MAL_EN,
+
+	JMSK_FE_VBPAD_CFG_BLOCK_ROW,
+
+	JMSK_PLNS_RD_OFFSET,
+	JMSK_PLNS_RD_BUF_SIZE_WIDTH,
+	JMSK_PLNS_RD_BUF_SIZE_HEIGHT,
+	JMSK_PLNS_RD_STRIDE,
+	JMSK_PLNS_RD_HINIT,
+	JMSK_PLNS_RD_VINIT,
+
+	JMSK_WE_CFG_BYTE_ORDERING,
+	JMSK_WE_CFG_BURST_LENGTH_MAX,
+	JMSK_WE_CFG_MEMORY_FORMAT,
+	JMSK_WE_CFG_CBCR_ORDER,
+	JMSK_WE_CFG_PLN0_EN,
+	JMSK_WE_CFG_PLN1_EN,
+	JMSK_WE_CFG_PLN2_EN,
+	JMSK_WE_CFG_MAL_BOUNDARY,
+	JMSK_WE_CFG_MAL_EN,
+	JMSK_WE_CFG_POP_BUFF_ON_EOS,
+
+	JMSK_PLNS_WR_BUF_SIZE_WIDTH,
+	JMSK_PLNS_WR_BUF_SIZE_HEIGHT,
+
+	JMSK_PLNS_WR_STRIDE,
+	JMSK_PLNS_WR_HINIT,
+	JMSK_PLNS_WR_VINIT,
+	JMSK_PLNS_WR_HSTEP,
+	JMSK_PLNS_WR_VSTEP,
+	JMSK_PLNS_WR_BLOCK_CFG_PER_COL,
+	JMSK_PLNS_WR_BLOCK_CFG_PER_RAW,
+
+	JMSK_ENC_CFG_IMAGE_FORMAT,
+	JMSK_ENC_CFG_APPLY_EOI,
+	JMSK_ENC_CFG_HUFFMAN_SEL,
+	JMSK_ENC_CFG_FSC_ENABLE,
+	JMSK_ENC_CFG_OUTPUT_DISABLE,
+	JMSK_ENC_CFG_RST_MARKER_PERIOD,
+	JMSK_ENC_IMAGE_SIZE_WIDTH,
+	JMSK_ENC_IMAGE_SIZE_HEIGHT,
+
+	JMSK_SCALE_CFG_HSCALE_ENABLE,
+	JMSK_SCALE_CFG_VSCALE_ENABLE,
+	JMSK_SCALE_CFG_UPSAMPLE_EN,
+	JMSK_SCALE_CFG_SUBSAMPLE_EN,
+	JMSK_SCALE_CFG_HSCALE_ALGO,
+	JMSK_SCALE_CFG_VSCALE_ALGO,
+	JMSK_SCALE_CFG_H_SCALE_FIR_ALGO,
+	JMSK_SCALE_CFG_V_SCALE_FIR_ALGO,
+
+	JMSK_SCALE_PLNS_OUT_CFG_BLK_WIDTH,
+	JMSK_SCALE_PLNS_OUT_CFG_BLK_HEIGHT,
+
+	JMSK_SCALE_PLNS_HSTEP_FRACTIONAL,
+	JMSK_SCALE_PLNS_HSTEP_INTEGER,
+	JMSK_SCALE_PLNS_VSTEP_FRACTIONAL,
+	JMSK_SCALE_PLNS_VSTEP_INTEGER,
+
+	JMSK_DMI_CFG,
+	JMSK_DMI_ADDR,
+	JMSK_DMI_DATA,
+
+	JMSK_TESTBUS_CFG,
+	JMSK_FE_VBPAD_CFG,
+
+	JMSK_PLN0_RD_HINIT_INT,
+	JMSK_PLN1_RD_HINIT_INT,
+	JMSK_PLN2_RD_HINIT_INT,
+	JMSK_PLN0_RD_VINIT_INT,
+	JMSK_PLN1_RD_VINIT_INT,
+	JMSK_PLN2_RD_VINIT_INT,
+	JMSK_ID_MAX
+};
+
+struct qcom_jpeg_reg_offs {
+	u32 hw_version;
+	u32 hw_capability;
+	u32 reset_cmd;
+	u32 core_cfg;
+	u32 int_mask;
+	u32 int_clr;
+	u32 int_status;
+	u32 hw_cmd;
+	u32 enc_core_state;
+
+	struct {
+		u32 pntr[QCOM_JPEG_MAX_PLANES];
+		u32 offs[QCOM_JPEG_MAX_PLANES];
+		u32 cnsmd[QCOM_JPEG_MAX_PLANES];
+		u32 bsize[QCOM_JPEG_MAX_PLANES];
+		u32 stride[QCOM_JPEG_MAX_PLANES];
+		u32 hinit[QCOM_JPEG_MAX_PLANES];
+		u32 vinit[QCOM_JPEG_MAX_PLANES];
+		u32 pntr_cnt;
+		u32 vbpad_cfg;
+	} fe;
+	u32 fe_cfg;
+
+	struct {
+		u32 pntr[QCOM_JPEG_MAX_PLANES];
+		u32 cnsmd[QCOM_JPEG_MAX_PLANES];
+		u32 bsize[QCOM_JPEG_MAX_PLANES];
+		u32 stride[QCOM_JPEG_MAX_PLANES];
+		u32 hinit[QCOM_JPEG_MAX_PLANES];
+		u32 hstep[QCOM_JPEG_MAX_PLANES];
+		u32 vinit[QCOM_JPEG_MAX_PLANES];
+		u32 vstep[QCOM_JPEG_MAX_PLANES];
+		u32 blocks[QCOM_JPEG_MAX_PLANES];
+		u32 pntr_cnt;
+	} we;
+	u32 we_cfg;
+
+	struct {
+		u32 hstep[QCOM_JPEG_MAX_PLANES];
+		u32 vstep[QCOM_JPEG_MAX_PLANES];
+	} scale;
+	u32 scale_cfg;
+	u32 scale_out_cfg[QCOM_JPEG_MAX_PLANES];
+
+	u32 enc_cfg;
+	u32 enc_img_size;
+	u32 enc_out_size;
+
+	u32 dmi_cfg;
+	u32 dmi_data;
+	u32 dmi_addr;
+};
+
+#endif /* QCOM_JENC_DEFS_H_ */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
new file mode 100644
index 000000000000..c0ea42cc7af7
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/interconnect.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-mem2mem.h>
+
+#include "qcom_jenc_dev.h"
+
+#include "qcom_jenc_defs.h"
+#include "qcom_jenc_ops.h"
+#include "qcom_jenc_res.h"
+#include "qcom_jenc_v4l2.h"
+
+static int qcom_jpeg_clk_init(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_dev_resources *res = jenc->res;
+	int c_idx;
+
+	jenc->clks = devm_kcalloc(jenc->dev, ARRAY_SIZE(res->clk_names), sizeof(*jenc->clks),
+				  GFP_KERNEL);
+	if (!jenc->clks)
+		return -ENOMEM;
+
+	for (c_idx = 0; c_idx < ARRAY_SIZE(res->clk_names); c_idx++) {
+		if (!res->clk_names[c_idx])
+			break;
+
+		jenc->clks[c_idx].id = res->clk_names[c_idx];
+		jenc->num_clks++;
+	}
+
+	if (!jenc->num_clks)
+		return -EINVAL;
+
+	return devm_clk_bulk_get(jenc->dev, jenc->num_clks, jenc->clks);
+}
+
+static int qcom_jpeg_clk_rate(struct qcom_jenc_dev *jenc, enum qcom_soc_perf_level level)
+{
+	const struct qcom_dev_resources	*res = jenc->res;
+	const struct qcom_perf_resource	*perf = &res->perf_cfg[level];
+	int c_idx;
+	int rc = 0;
+
+	for (c_idx = 0; c_idx < jenc->num_clks; c_idx++) {
+		/* skip clocks with fixed or default frequency */
+		if (!perf->clk_rate[c_idx])
+			continue;
+
+		/* setup frequency according to performance level */
+		rc = clk_set_rate(jenc->clks[c_idx].clk, perf->clk_rate[c_idx]);
+		if (rc < 0) {
+			dev_err(jenc->dev, "clock set rate failed: %d\n", rc);
+			return rc;
+		}
+
+		dev_dbg(jenc->dev, "clock %s current rate: %ld\n",
+			jenc->clks[c_idx].id, clk_get_rate(jenc->clks[c_idx].clk));
+	}
+
+	return rc;
+}
+
+static int qcom_jpeg_clk_on(struct qcom_jenc_dev *jenc)
+{
+	int rc;
+
+	rc = qcom_jpeg_clk_rate(jenc, jenc->perf);
+	if (rc)
+		return rc;
+
+	rc = clk_bulk_prepare_enable(jenc->num_clks, jenc->clks);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static void qcom_jpeg_clk_off(struct qcom_jenc_dev *jenc)
+{
+	clk_bulk_disable_unprepare(jenc->num_clks, jenc->clks);
+}
+
+static int qcom_jpeg_icc_on(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_dev_resources	*res = jenc->res;
+	int p_idx;
+	int rc;
+
+	for (p_idx = 0; p_idx < res->num_of_icc; p_idx++) {
+		rc = icc_set_bw(jenc->icc_paths[p_idx], res->icc_res[p_idx].pair.aggr,
+				res->icc_res[p_idx].pair.peak);
+		if (rc) {
+			dev_err(jenc->dev, "%s failed for path %s: %d\n", __func__,
+				res->icc_res[p_idx].icc_id, rc);
+			goto err_icc_set_bw;
+		}
+	}
+
+	return 0;
+
+err_icc_set_bw:
+	while (--p_idx >= 0)
+		icc_set_bw(jenc->icc_paths[p_idx], 0, 0);
+
+	return rc;
+}
+
+static void qcom_jpeg_icc_off(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_dev_resources	*res = jenc->res;
+	int p_idx;
+
+	for (p_idx = 0; p_idx < res->num_of_icc; p_idx++)
+		icc_set_bw(jenc->icc_paths[p_idx], 0, 0);
+}
+
+static int qcom_jpeg_icc_init(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_dev_resources	*res = jenc->res;
+	int p_idx;
+
+	jenc->icc_paths = devm_kcalloc(jenc->dev, res->num_of_icc, sizeof(*jenc->icc_paths),
+				       GFP_KERNEL);
+	if (!jenc->icc_paths)
+		return -ENOMEM;
+
+	for (p_idx = 0; p_idx < res->num_of_icc; p_idx++) {
+		jenc->icc_paths[p_idx] = devm_of_icc_get(jenc->dev, res->icc_res[p_idx].icc_id);
+		if (IS_ERR(jenc->icc_paths[p_idx])) {
+			return dev_err_probe(jenc->dev, PTR_ERR(jenc->icc_paths[p_idx]),
+					     "failed to get ICC path: %pe\n",
+					     jenc->icc_paths[p_idx]);
+		}
+	}
+
+	return 0;
+}
+
+static __maybe_unused int qcom_jpeg_pm_suspend(struct device *dev)
+{
+	struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+
+	qcom_jpeg_clk_off(jenc);
+
+	qcom_jpeg_icc_off(jenc);
+
+	return 0;
+}
+
+static __maybe_unused int qcom_jpeg_pm_resume(struct device *dev)
+{
+	struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+	int rc;
+
+	rc = qcom_jpeg_icc_on(jenc);
+	if (rc)
+		return rc;
+
+	rc = qcom_jpeg_clk_on(jenc);
+	if (rc) {
+		qcom_jpeg_icc_off(jenc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static __maybe_unused int qcom_jpeg_suspend(struct device *dev)
+{
+	struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+	int rc;
+
+	v4l2_m2m_suspend(jenc->m2m_dev);
+
+	rc = pm_runtime_force_suspend(dev);
+	if (rc)
+		v4l2_m2m_resume(jenc->m2m_dev);
+
+	return rc;
+}
+
+static __maybe_unused int qcom_jpeg_resume(struct device *dev)
+{
+	struct qcom_jenc_dev *jenc = dev_get_drvdata(dev);
+	int rc;
+
+	rc = pm_runtime_force_resume(dev);
+	if (rc)
+		return rc;
+
+	v4l2_m2m_resume(jenc->m2m_dev);
+
+	return 0;
+}
+
+static const struct dev_pm_ops qcom_jpeg_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(qcom_jpeg_suspend, qcom_jpeg_resume)
+	SET_RUNTIME_PM_OPS(qcom_jpeg_pm_suspend, qcom_jpeg_pm_resume, NULL)
+};
+
+static int qcom_jpeg_probe(struct platform_device *pdev)
+{
+	const struct qcom_dev_resources *res;
+	struct qcom_jenc_dev *jenc;
+	int rc;
+
+	jenc = devm_kzalloc(&pdev->dev, sizeof(*jenc), GFP_KERNEL);
+	if (!jenc)
+		return -ENOMEM;
+
+	jenc->dev = &pdev->dev;
+	mutex_init(&jenc->dev_mutex);
+	spin_lock_init(&jenc->hw_lock);
+	init_completion(&jenc->reset_complete);
+	init_completion(&jenc->stop_complete);
+
+	res = device_get_match_data(jenc->dev);
+	if (!res)
+		return dev_err_probe(jenc->dev, -ENODEV, "unsupported SoC\n");
+	jenc->res = res;
+
+	if (!jenc->res->hw_offs || !jenc->res->hw_ops)
+		return dev_err_probe(jenc->dev, -EINVAL, "missing hw resources\n");
+
+	rc = dma_set_mask_and_coherent(jenc->dev, DMA_BIT_MASK(32));
+	if (rc)
+		return dev_err_probe(jenc->dev, rc, "failed to set DMA mask\n");
+
+	jenc->jpeg_base = devm_platform_ioremap_resource_byname(pdev, "jpeg");
+	if (IS_ERR(jenc->jpeg_base))
+		return dev_err_probe(jenc->dev, PTR_ERR(jenc->jpeg_base),
+				     "failed to map JPEG resource\n");
+
+	rc = qcom_jpeg_clk_init(jenc);
+	if (rc)
+		return rc;
+
+	jenc->irq = platform_get_irq(pdev, 0);
+	if (jenc->irq < 0)
+		return dev_err_probe(jenc->dev, jenc->irq, "failed to get IRQ\n");
+
+	rc = devm_request_threaded_irq(jenc->dev, jenc->irq,
+				       jenc->res->hw_ops->hw_irq_top,
+				       jenc->res->hw_ops->hw_irq_bot,
+				       IRQF_ONESHOT, dev_name(jenc->dev), jenc);
+	if (rc)
+		return dev_err_probe(jenc->dev, rc, "failed to request IRQ\n");
+
+	rc = qcom_jpeg_icc_init(jenc);
+	if (rc)
+		return rc;
+
+	rc = v4l2_device_register(jenc->dev, &jenc->v4l2_dev);
+	if (rc) {
+		dev_err(jenc->dev, "failed to register V4L2 device\n");
+		return rc;
+	}
+
+	jenc->perf = QCOM_SOC_PERF_NOMINAL;
+
+	rc = qcom_jpeg_v4l2_register(jenc);
+	if (rc) {
+		dev_err(jenc->dev, "failed to register video device\n");
+		goto err_v4l2_device_unregister;
+	}
+
+	rc = devm_pm_runtime_enable(jenc->dev);
+	if (rc)
+		goto err_v4l2_unregister;
+
+	dev_dbg(jenc->dev, "Qualcomm JPEG encoder registered\n");
+
+	platform_set_drvdata(pdev, jenc);
+
+	return 0;
+
+err_v4l2_unregister:
+	qcom_jpeg_v4l2_unregister(jenc);
+err_v4l2_device_unregister:
+	v4l2_device_unregister(&jenc->v4l2_dev);
+	return rc;
+}
+
+static void qcom_jpeg_remove(struct platform_device *pdev)
+{
+	struct qcom_jenc_dev *jenc = platform_get_drvdata(pdev);
+
+	qcom_jpeg_v4l2_unregister(jenc);
+
+	v4l2_device_unregister(&jenc->v4l2_dev);
+
+	dev_dbg(jenc->dev, "Qualcomm JPEG encoder deregistered\n");
+}
+
+static const struct of_device_id qcom_jpeg_of_match[] = {
+	{
+		.compatible	= "qcom,sm8250-jenc",
+		.data		= &qcom_t165_t480_jpeg_drvdata
+	},
+	{
+		.compatible	= "qcom,qcm6490-jenc",
+		.data		= &qcom_t680_jpeg_drvdata
+	},
+	{
+		.compatible	= "qcom,sm8550-jenc",
+		.data		= &qcom_t780_jpeg_drvdata
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, qcom_jpeg_of_match);
+
+static struct platform_driver qcom_jpeg_platform_driver = {
+	.probe  = qcom_jpeg_probe,
+	.remove = qcom_jpeg_remove,
+	.driver = {
+		.name = QCOM_JPEG_ENC_NAME,
+		.of_match_table = qcom_jpeg_of_match,
+		.pm             = &qcom_jpeg_pm_ops,
+	},
+};
+
+module_platform_driver(qcom_jpeg_platform_driver);
+
+MODULE_DESCRIPTION("QCOM JPEG mem2mem V4L2 encoder");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
new file mode 100644
index 000000000000..151e38c4de82
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_DEV_H
+#define QCOM_JENC_DEV_H
+
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/irqreturn.h>
+#include <linux/interconnect.h>
+
+#include <media/videobuf2-core.h>
+
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-ctrls.h>
+
+#include "qcom_jenc_res.h"
+#include "qcom_jenc_hdr.h"
+#include "qcom_jenc_defs.h"
+
+#define QCOM_JPEG_ENC_NAME "qcom-jpeg-enc"
+
+#define TYPE2QID(t) \
+	(V4L2_TYPE_IS_OUTPUT(t) ? JENC_SRC_QUEUE : JENC_DST_QUEUE)
+
+enum qcom_enc_qid {
+	JENC_SRC_QUEUE = 0,
+	JENC_DST_QUEUE,
+	JENC_QUEUE_MAX
+};
+
+struct jenc_enc_format {
+	u32 type;
+	u32 fourcc;
+};
+
+struct qcom_jpeg_buff {
+	struct {
+		struct sg_table		*sgt;
+		dma_addr_t		dma;
+		unsigned long		size;
+
+	} plns[QCOM_JPEG_MAX_PLANES];
+};
+
+struct qcom_jenc_queue {
+	struct v4l2_pix_format_mplane	vf;
+	u32				sequence;
+	struct qcom_jpeg_buff		buff[VB2_MAX_FRAME];
+	int				buff_id;
+};
+
+struct qcom_jenc_dev {
+	struct device			*dev;
+	struct v4l2_device		v4l2_dev;
+	struct v4l2_m2m_dev		*m2m_dev;
+	struct video_device		*vdev;
+	const struct qcom_dev_resources	*res;
+	enum qcom_soc_perf_level	perf;
+	int				irq;
+	void __iomem			*jpeg_base;
+	struct clk_bulk_data		*clks;
+	int				num_clks;
+	/* device mutex lock */
+	struct mutex			dev_mutex;
+	atomic_t			ref_count;
+	struct completion		reset_complete;
+	struct completion		stop_complete;
+	/* decoder hardware lock */
+	spinlock_t			hw_lock;
+	struct jenc_context		*actx;
+	struct icc_path			**icc_paths;
+
+	u32				pending_irq_status;
+
+	void (*enc_hw_irq_cb)
+		(void *data, enum vb2_buffer_state ev, size_t out_size);
+};
+
+struct jenc_context {
+	struct device		 *dev;
+	struct qcom_jenc_dev	 *jenc;
+	struct v4l2_fh		 fh;
+
+	/* quality update lock */
+	struct mutex		 quality_mutex;
+	struct v4l2_ctrl	 *quality_ctl;
+	u32			 quality_requested;
+	u32			 quality_programmed;
+	struct v4l2_ctrl_handler ctrl_hdl;
+
+	/* session context lock */
+	struct mutex		 ctx_lock;
+
+	bool			 is_stopping;
+
+	struct qcom_jenc_queue	bufq[JENC_QUEUE_MAX];
+	struct qcom_jenc_header	hdr_cache;
+
+	struct work_struct finish_work;
+	struct work_struct stop_work;
+};
+
+#endif
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c
new file mode 100644
index 000000000000..63826d5cab56
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include <media/jpeg.h>
+#include <media/v4l2-jpeg.h>
+
+#include "qcom_jenc_hdr.h"
+#include "qcom_jenc_dev.h"
+
+/*
+ * The elements defined in this header are specified
+ * in the ITU-T T.81 / JPEG specification.
+ *
+ * https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ */
+
+#define JFIF_HEADER_WIDTH_OFFS		0x07
+#define JFIF_HEADER_HEIGHT_OFFS		0x05
+
+#define JPEG_MARKER_PREFIX		0xff
+
+#define JFIF_APP0_LENGTH_HI		0x00
+#define JFIF_APP0_LENGTH_LO		0x10
+#define JFIF_IDENT_TERM		0x00
+#define JFIF_VERSION_MAJOR		0x01
+#define JFIF_VERSION_MINOR		0x01
+#define JFIF_DENSITY_HI			0x00
+#define JFIF_DENSITY_LO			0x01
+#define JFIF_THUMBNAIL_SIZE		0x00
+
+#define JPEG_SEG_LEN_HI			0x00
+#define JPEG_LEN_DQT_LUMA_LO		0x43
+#define JPEG_LEN_DQT_CHROMA_LO		0x84
+#define JPEG_LEN_SOF0_MONO_LO		0x0b
+#define JPEG_LEN_SOF0_COLOR_LO		0x11
+#define JPEG_LEN_DHT_MONO_LO		0xd2
+#define JPEG_LEN_DHT_COLOR_HI		0x01
+#define JPEG_LEN_DHT_COLOR_LO		0xa2
+#define JPEG_LEN_SOS_MONO_LO		0x08
+#define JPEG_LEN_SOS_COLOR_LO		0x0c
+
+#define JPEG_SAMPLE_PRECISION_BITS	0x08
+#define JPEG_COMP_MONO			1
+#define JPEG_COMP_COLOR		3
+
+#define JPEG_SAMPLING_H1V1		0x11
+#define JPEG_SAMPLING_H2V2		0x22
+
+#define JPEG_QTABLE_LUMA		0
+#define JPEG_QTABLE_CHROMA		1
+
+#define JPEG_DC_HT_INDEX_LUMA		0x00
+#define JPEG_DC_HT_INDEX_CHROMA		0x01
+#define JPEG_AC_HT_INDEX_LUMA		0x10
+#define JPEG_AC_HT_INDEX_CHROMA		0x11
+
+#define JPEG_SPECTRAL_START		0x00
+#define JPEG_SPECTRAL_END		0x3f
+#define JPEG_APPROX_HIGH_LOW		0x00
+#define JPEG_TABLE_SEL_LUMA		0x00
+#define JPEG_TABLE_SEL_CHROMA		0x11
+
+struct jpeg_header_buf {
+	u8  *ptr;
+	u32 size;
+	u32 pos;
+};
+
+static const struct jpeg_soi_app0 soi_app0 = {
+	.soi		= { JPEG_MARKER_PREFIX, JPEG_MARKER_SOI },
+	.app0_marker	= { JPEG_MARKER_PREFIX, JPEG_MARKER_APP0 },
+	.app0_length	= { JFIF_APP0_LENGTH_HI, JFIF_APP0_LENGTH_LO },
+	.jfif_id	= { 'J', 'F', 'I', 'F', JFIF_IDENT_TERM },
+	.version	= { JFIF_VERSION_MAJOR, JFIF_VERSION_MINOR },
+	.units		= 0x00,
+	.density_x	= { JFIF_DENSITY_HI, JFIF_DENSITY_LO },
+	.density_y	= { JFIF_DENSITY_HI, JFIF_DENSITY_LO },
+	.thumb_x	= JFIF_THUMBNAIL_SIZE,
+	.thumb_y	= JFIF_THUMBNAIL_SIZE,
+};
+
+static const struct jpeg_record_hdr dqt_luma_hdr = {
+	.marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_DQT },
+	.length = { JPEG_SEG_LEN_HI, JPEG_LEN_DQT_LUMA_LO }
+};
+
+/* Luminance quantization table */
+static const struct jpeg_dqt_header dqt_luma_data = {
+	.index = 0x00,
+};
+
+static const struct jpeg_record_hdr  dqt_chroma_hdr = {
+	.marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_DQT },
+	.length = { JPEG_SEG_LEN_HI, JPEG_LEN_DQT_CHROMA_LO }
+};
+
+/* Chrominance quantization table */
+static const struct jpeg_dqt_header dqt_chroma_data = {
+	.index = 0x01,
+};
+
+static const struct jpeg_record_hdr  sof0_mono_hdr = {
+	.marker	= { JPEG_MARKER_PREFIX, JPEG_MARKER_SOF0 },
+	.length	= { JPEG_SEG_LEN_HI, JPEG_LEN_SOF0_MONO_LO },
+};
+
+static const struct jpeg_sof0_mono sof0_mono_data = {
+	.precision	= JPEG_SAMPLE_PRECISION_BITS,
+	.height		= { 0x00, 0x00 },
+	.width		= { 0x00, 0x00 },
+	.components	= JPEG_COMP_MONO,
+	.y_id		= 1,
+	.y_sampling	= JPEG_SAMPLING_H1V1,
+	.y_qtable	= JPEG_QTABLE_LUMA,
+};
+
+static const struct jpeg_record_hdr  sof0_color_hdr = {
+	.marker	= { JPEG_MARKER_PREFIX, JPEG_MARKER_SOF0 },
+	.length	= { JPEG_SEG_LEN_HI, JPEG_LEN_SOF0_COLOR_LO },
+};
+
+static const struct jpeg_sof0_color sof0_color_data = {
+	.precision	= JPEG_SAMPLE_PRECISION_BITS,
+	.height		= { 0x00, 0x00 },
+	.width		= { 0x00, 0x00 },
+	.components	= JPEG_COMP_COLOR,
+	.y_id		= 1,
+	.y_sampling	= JPEG_SAMPLING_H2V2,
+	.y_qtable	= JPEG_QTABLE_LUMA,
+	.cb_id		= 2,
+	.cb_sampling	= JPEG_SAMPLING_H1V1,
+	.cb_qtable	= JPEG_QTABLE_CHROMA,
+	.cr_id		= 3,
+	.cr_sampling	= JPEG_SAMPLING_H1V1,
+	.cr_qtable	= JPEG_QTABLE_CHROMA,
+};
+
+static const struct jpeg_record_hdr coeff_mono_hdr = {
+	.marker = { JPEG_MARKER_PREFIX, JPEG_MARKER_DHT },
+	.length = { JPEG_SEG_LEN_HI, JPEG_LEN_DHT_MONO_LO },
+};
+
+static const struct jpeg_record_hdr coeff_color_hdr = {
+	.marker	= { JPEG_MARKER_PREFIX, JPEG_MARKER_DHT },
+	.length	= { JPEG_LEN_DHT_COLOR_HI, JPEG_LEN_DHT_COLOR_LO },
+};
+
+static const struct jpeg_record_hdr sos_mono_hdr = {
+	.marker	= { JPEG_MARKER_PREFIX, JPEG_MARKER_SOS },
+	.length	= { JPEG_SEG_LEN_HI, JPEG_LEN_SOS_MONO_LO },
+};
+
+static const struct jpeg_sos_mono sos_mono_data = {
+	.components	= JPEG_COMP_MONO,
+	.y_id		= 1,
+	.y_tables	= JPEG_TABLE_SEL_LUMA,
+	.spectral	= { JPEG_SPECTRAL_START, JPEG_SPECTRAL_END },
+	.approx		= JPEG_APPROX_HIGH_LOW,
+};
+
+static const struct jpeg_record_hdr sos_color_hdr = {
+	.marker	= { JPEG_MARKER_PREFIX, JPEG_MARKER_SOS },
+	.length	= { JPEG_SEG_LEN_HI, JPEG_LEN_SOS_COLOR_LO },
+};
+
+static const struct jpeg_sos_color sos_color_data = {
+	.components	= JPEG_COMP_COLOR,
+	.y_id		= 1,
+	.y_tables	= JPEG_TABLE_SEL_LUMA,
+	.cb_id		= 2,
+	.cb_tables	= JPEG_TABLE_SEL_CHROMA,
+	.cr_id		= 3,
+	.cr_tables	= JPEG_TABLE_SEL_CHROMA,
+	.spectral	= { JPEG_SPECTRAL_START, JPEG_SPECTRAL_END },
+	.approx		= JPEG_APPROX_HIGH_LOW,
+};
+
+static inline int jb_put_mem(struct jpeg_header_buf *hdr, const void *src, u32 len)
+{
+	if (len > hdr->size - hdr->pos)
+		return -ENOSPC;
+
+	memcpy(hdr->ptr + hdr->pos, src, len);
+	hdr->pos += len;
+
+	return 0;
+}
+
+static int jb_put_dht(struct jpeg_header_buf *hdr, u8 index, const u8 *table, u32 len)
+{
+	u8 data[1 + V4L2_JPEG_REF_HT_AC_LEN];
+
+	if (len > V4L2_JPEG_REF_HT_AC_LEN)
+		return -EINVAL;
+
+	data[0] = index;
+	memcpy(&data[1], table, len);
+
+	return jb_put_mem(hdr, data, len + 1);
+}
+
+static inline void patch_u16be(u8 *buf, u32 off, u16 v)
+{
+	buf[off]	= (v >> 8) & 0xff;
+	buf[off + 1]	=  v & 0xff;
+}
+
+int qcom_jenc_header_init(struct qcom_jenc_header *c, u32 fourcc)
+{
+	int rc;
+	struct jpeg_header_buf hdr = {
+		.ptr = c->data,
+		.size = sizeof(c->data),
+		.pos = 0,
+	};
+
+	c->sof_offset	= 0;
+	c->dqt_one_offs = 0;
+	c->dqt_two_offs = 0;
+
+	rc = jb_put_mem(&hdr, &soi_app0, sizeof(soi_app0));
+	if (rc)
+		return rc;
+
+	if (fourcc != V4L2_PIX_FMT_GREY) {
+		rc = jb_put_mem(&hdr, &dqt_chroma_hdr, sizeof(dqt_chroma_hdr));
+		if (rc)
+			return rc;
+
+		/* Store the offset of the first DQT table for later use. */
+		c->dqt_one_offs = hdr.pos;
+		rc = jb_put_mem(&hdr, &dqt_luma_data, sizeof(dqt_luma_data));
+		if (rc)
+			return rc;
+
+		/* Store the offset of the second DQT table for later use. */
+		c->dqt_two_offs = hdr.pos;
+		rc = jb_put_mem(&hdr, &dqt_chroma_data, sizeof(dqt_chroma_data));
+		if (rc)
+			return rc;
+	} else {
+		rc = jb_put_mem(&hdr, &dqt_luma_hdr, sizeof(dqt_luma_hdr));
+		if (rc)
+			return rc;
+
+		/* Store the offset of the first DQT table for later use. */
+		c->dqt_one_offs = hdr.pos;
+		rc = jb_put_mem(&hdr, &dqt_luma_data, sizeof(dqt_luma_data));
+		if (rc)
+			return rc;
+	}
+
+	/* Store the offset of the SOF record for later use. */
+	c->sof_offset = hdr.pos;
+
+	if (fourcc != V4L2_PIX_FMT_GREY) {
+		rc = jb_put_mem(&hdr, &sof0_color_hdr, sizeof(sof0_color_hdr));
+		if (rc)
+			return rc;
+		rc = jb_put_mem(&hdr, &sof0_color_data, sizeof(sof0_color_data));
+		if (rc)
+			return rc;
+		rc = jb_put_mem(&hdr, &coeff_color_hdr, sizeof(coeff_color_hdr));
+		if (rc)
+			return rc;
+		rc = jb_put_dht(&hdr, JPEG_DC_HT_INDEX_LUMA,
+				v4l2_jpeg_ref_table_luma_dc_ht,
+				ARRAY_SIZE(v4l2_jpeg_ref_table_luma_dc_ht));
+		if (rc)
+			return rc;
+		rc = jb_put_dht(&hdr, JPEG_AC_HT_INDEX_LUMA,
+				v4l2_jpeg_ref_table_luma_ac_ht,
+				ARRAY_SIZE(v4l2_jpeg_ref_table_luma_ac_ht));
+		if (rc)
+			return rc;
+		rc = jb_put_dht(&hdr, JPEG_DC_HT_INDEX_CHROMA,
+				v4l2_jpeg_ref_table_chroma_dc_ht,
+				ARRAY_SIZE(v4l2_jpeg_ref_table_chroma_dc_ht));
+		if (rc)
+			return rc;
+		rc = jb_put_dht(&hdr, JPEG_AC_HT_INDEX_CHROMA,
+				v4l2_jpeg_ref_table_chroma_ac_ht,
+				ARRAY_SIZE(v4l2_jpeg_ref_table_chroma_ac_ht));
+		if (rc)
+			return rc;
+		rc = jb_put_mem(&hdr, &sos_color_hdr, sizeof(sos_color_hdr));
+		if (rc)
+			return rc;
+		rc = jb_put_mem(&hdr, &sos_color_data, sizeof(sos_color_data));
+		if (rc)
+			return rc;
+	} else {
+		rc = jb_put_mem(&hdr, &sof0_mono_hdr, sizeof(sof0_mono_hdr));
+		if (rc)
+			return rc;
+		rc = jb_put_mem(&hdr, &sof0_mono_data, sizeof(sof0_mono_data));
+		if (rc)
+			return rc;
+		rc = jb_put_mem(&hdr, &coeff_mono_hdr, sizeof(coeff_mono_hdr));
+		if (rc)
+			return rc;
+		rc = jb_put_dht(&hdr, JPEG_DC_HT_INDEX_LUMA,
+				v4l2_jpeg_ref_table_luma_dc_ht,
+				ARRAY_SIZE(v4l2_jpeg_ref_table_luma_dc_ht));
+		if (rc)
+			return rc;
+		rc = jb_put_dht(&hdr, JPEG_AC_HT_INDEX_LUMA,
+				v4l2_jpeg_ref_table_luma_ac_ht,
+				ARRAY_SIZE(v4l2_jpeg_ref_table_luma_ac_ht));
+		if (rc)
+			return rc;
+		rc = jb_put_mem(&hdr, &sos_mono_hdr, sizeof(sos_mono_hdr));
+		if (rc)
+			return rc;
+		rc = jb_put_mem(&hdr, &sos_mono_data, sizeof(sos_mono_data));
+		if (rc)
+			return rc;
+	}
+
+	c->size = hdr.pos;
+
+	return 0;
+}
+
+void qcom_jenc_dqts_emit(const struct qcom_jenc_header *c, u8 *dst)
+{
+	/* Propagate DQT tables into the JPEG header */
+	if (c->dqt_one_offs) {
+		u32 one_offs = c->dqt_one_offs + sizeof(dqt_luma_data.index);
+
+		memcpy(dst + one_offs, &c->data[one_offs], sizeof(dqt_luma_data.value));
+	}
+
+	if (c->dqt_two_offs) {
+		u32 two_offs = c->dqt_two_offs + sizeof(dqt_chroma_data.index);
+
+		memcpy(dst + two_offs, &c->data[two_offs], sizeof(dqt_chroma_data.value));
+	}
+}
+
+u32 qcom_jenc_header_emit(const struct qcom_jenc_header *c, u8 *dst, u32 dst_size, u16 width,
+			  u16 height)
+{
+	/* Copy JFIF into JPEG header and update actual image size */
+	if (dst_size < c->size)
+		return 0;
+
+	memcpy(dst, c->data, c->size);
+
+	/* Update output image size */
+	patch_u16be(dst, c->sof_offset + JFIF_HEADER_WIDTH_OFFS, width);
+	patch_u16be(dst, c->sof_offset + JFIF_HEADER_HEIGHT_OFFS, height);
+
+	return c->size;
+}
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h
new file mode 100644
index 000000000000..90eb4defcef0
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_HDR_H
+#define QCOM_JENC_HDR_H
+
+#include <linux/types.h>
+
+#include <media/v4l2-jpeg.h>
+
+#include "qcom_jenc_defs.h"
+
+#define JPEG_HEADER_MAX	1024
+
+struct qcom_jenc_header {
+	u8  data[JPEG_HEADER_MAX];
+	u32 size;
+	u32 sof_offset;
+	u32 dqt_one_offs;
+	u32 dqt_two_offs;
+};
+
+struct jpeg_record_hdr {
+	u8 marker[2];
+	u8 length[2];
+} __packed;
+
+struct jpeg_dqt_header {
+	u8 index;
+	u8 value[V4L2_JPEG_PIXELS_IN_BLOCK];
+} __packed;
+
+struct jpeg_soi_app0 {
+	u8 soi[2];
+	u8 app0_marker[2];
+	u8 app0_length[2];
+	u8 jfif_id[5];
+	u8 version[2];
+	u8 units;
+	u8 density_x[2];
+	u8 density_y[2];
+	u8 thumb_x;
+	u8 thumb_y;
+} __packed;
+
+struct jpeg_sof0_mono {
+	u8 precision;
+	u8 height[2];
+	u8 width[2];
+	u8 components;
+
+	u8 y_id;
+	u8 y_sampling;
+	u8 y_qtable;
+} __packed;
+
+struct jpeg_sof0_color {
+	u8 precision;
+	u8 height[2];
+	u8 width[2];
+	u8 components;
+
+	u8 y_id;
+	u8 y_sampling;
+	u8 y_qtable;
+
+	u8 cb_id;
+	u8 cb_sampling;
+	u8 cb_qtable;
+
+	u8 cr_id;
+	u8 cr_sampling;
+	u8 cr_qtable;
+} __packed;
+
+struct jpeg_sos_hdr {
+	u8 sos_marker[2];
+	u8 sos_length[2];
+	u8 components;
+} __packed;
+
+struct jpeg_sos_mono {
+	u8 components;
+
+	u8 y_id;
+	u8 y_tables;
+
+	u8 spectral[2];
+	u8 approx;
+} __packed;
+
+struct jpeg_sos_color {
+	u8 components;
+
+	u8 y_id;
+	u8 y_tables;
+
+	u8 cb_id;
+	u8 cb_tables;
+
+	u8 cr_id;
+	u8 cr_tables;
+
+	u8 spectral[2];
+	u8 approx;
+} __packed;
+
+struct jenc_context;
+
+int qcom_jenc_header_init(struct qcom_jenc_header *c, u32 fourcc);
+
+void qcom_jenc_dqts_emit(const struct qcom_jenc_header *c, u8 *dst);
+
+u32 qcom_jenc_header_emit(const struct qcom_jenc_header *c, u8 *dst, u32 dst_size, u16 width,
+			  u16 height);
+
+#endif /* QCOM_JENC_HDR_H */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
new file mode 100644
index 000000000000..acfa24c663fc
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
@@ -0,0 +1,1658 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <asm/div64.h>
+#include <linux/pm_runtime.h>
+#include <linux/scatterlist.h>
+
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "qcom_jenc_dev.h"
+#include "qcom_jenc_ops.h"
+#include "qcom_jenc_defs.h"
+
+#define JPEG_RESET_TIMEOUT_MS	300
+#define JPEG_STOP_TIMEOUT_MS	200
+
+#define JPEG_DQT_SHIFT		20
+#define JPEG_Q5_21_SHIFT	21
+
+#define JPEG_MCU_BLOCK_8	8
+#define JPEG_MCU_BLOCK_16	16
+#define JPEG_MCU_BLOCK_128	128
+#define JPEG_MCU_BLOCK_256	256
+
+#define JPEG_DEFAULT_SCALE_STEP	0x200000
+
+#define JPEG_CLR_U32	(0U)
+#define JPEG_SET_U32	(~0U)
+
+/*
+ *  JPEG | V4L2
+ *  ---- | -------
+ *  H1V1 | GREY
+ *  H1V2 | YUV422M
+ *  H2V1 | NV16M
+ *  H2V2 | NV12M
+ */
+enum qcom_jpeg_encode_fmt {
+	JPEG_ENCODE_H1V1 = 0,
+	JPEG_ENCODE_H1V2,
+	JPEG_ENCODE_H2V1,
+	JPEG_ENCODE_H2V2,
+	JPEG_ENCODE_MONO,
+};
+
+enum qcom_jpeg_memory_fmt {
+	JPEG_MEM_FMT_PLANAR	 = 0x0,
+	JPEG_MEM_FMT_PPLANAR	 = 0x1,
+	JPEG_MEM_FMT_MONO	 = 0x2,
+	JPEG_MEM_FMT_COEFFICIENT = 0x3
+};
+
+enum jpeg_mal_bounds {
+	JPEG_CFG_MAL_BOUND_32_BYTES	= 0x0,
+	JPEG_CFG_MAL_BOUND_64_BYTES	= 0x1,
+	JPEG_CFG_MAL_BOUND_128_BYTES	= 0x2,
+	JPEG_CFG_MAL_BOUND_256_BYTES	= 0x3,
+	JPEG_CFG_MAL_BOUND_512_BYTES	= 0x4,
+	JPEG_CFG_MAL_BOUND_1K_BYTES	= 0x5,
+	JPEG_CFG_MAL_BOUND_2K_BYTES	= 0x6,
+	JPEG_CFG_MAL_BOUND_4K_BYTES	= 0x7
+};
+
+struct qcom_jpeg_scale_blocks {
+	u8 w_block[QCOM_JPEG_MAX_PLANES];
+	u8 h_block[QCOM_JPEG_MAX_PLANES];
+};
+
+struct qcom_jpeg_mal_boundary {
+	u32 bytes;
+	int boundary;
+};
+
+struct qcom_jpeg_formats {
+	u32 fourcc;
+	enum qcom_jpeg_encode_fmt encode;
+	enum qcom_jpeg_memory_fmt memory;
+};
+
+/*
+ * Luminance quantization table defined by CCITT T.81.
+ * See: https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ */
+static const u8 t81k1_dct_luma_table[V4L2_JPEG_PIXELS_IN_BLOCK] = {
+	16,  11,  10,  16,  24,  40,  51,  61,
+	12,  12,  14,  19,  26,  58,  60,  55,
+	14,  13,  16,  24,  40,  57,  69,  56,
+	14,  17,  22,  29,  51,  87,  80,  62,
+	18,  22,  37,  56,  68, 109, 103,  77,
+	24,  35,  55,  64,  81, 104, 113,  92,
+	49,  64,  78,  87, 103, 121, 120, 101,
+	72,  92,  95,  98, 112, 100, 103,  99
+};
+
+/*
+ * Chrominance quantization table defined by CCITT T.81.
+ * See: https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ */
+static const u8 t81k2_dct_chroma_table[V4L2_JPEG_PIXELS_IN_BLOCK] = {
+	17,  18,  24,  47,  99,  99,  99,  99,
+	18,  21,  26,  66,  99,  99,  99,  99,
+	24,  26,  56,  99,  99,  99,  99,  99,
+	47,  66,  99,  99,  99,  99,  99,  99,
+	99,  99,  99,  99,  99,  99,  99,  99,
+	99,  99,  99,  99,  99,  99,  99,  99,
+	99,  99,  99,  99,  99,  99,  99,  99,
+	99,  99,  99,  99,  99,  99,  99,  99
+};
+
+/*
+ * Zig-zag scan order for quantized DCT coefficients
+ * as defined by CCITT T.81.
+ * See: https://www.w3.org/Graphics/JPEG/itu-t81.pdf
+ */
+static const u8 t81a6_dct_zig_zag_table[] = {
+	 0,  1,  5,  6, 14, 15, 27, 28,
+	 2,  4,  7, 13, 16, 26, 29, 42,
+	 3,  8, 12, 17, 25, 30, 41, 43,
+	 9, 11, 18, 24, 31, 40, 44, 53,
+	10, 19, 23, 32, 39, 45, 52, 54,
+	20, 22, 33, 38, 46, 51, 55, 60,
+	21, 34, 37, 47, 50, 56, 59, 61,
+	35, 36, 48, 49, 57, 58, 62, 63
+};
+
+static const u8 jpeg_mcu_per_ratio[] = {
+	0, /* MCU = 1, Ratio < 2x	 */
+	3, /* MCU = 0, 2x <= Ratio < 4x	 */
+	2, /* MCU = 0, 4x <= Ratio < 8x	 */
+	1, /* MCU = 0, 8x <= Ratio < 16x */
+	0, /* MCU = 0, Ratio > 16x	 */
+};
+
+static const struct qcom_jpeg_formats jpeg_encode_fmt[] = {
+	{
+		.fourcc = V4L2_PIX_FMT_GREY,
+		.encode = JPEG_ENCODE_MONO,
+		.memory = JPEG_MEM_FMT_MONO
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_JPEG,
+		.encode = JPEG_ENCODE_H1V1,
+		.memory = JPEG_MEM_FMT_PPLANAR
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_YUV422M,
+		.encode = JPEG_ENCODE_H1V2,
+		.memory = JPEG_MEM_FMT_PLANAR
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_YVU422M,
+		.encode = JPEG_ENCODE_H1V2,
+		.memory = JPEG_MEM_FMT_PLANAR
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV16M,
+		.encode = JPEG_ENCODE_H2V1,
+		.memory = JPEG_MEM_FMT_PPLANAR
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV61M,
+		.encode = JPEG_ENCODE_H2V1,
+		.memory = JPEG_MEM_FMT_PPLANAR
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV12M,
+		.encode = JPEG_ENCODE_H2V2,
+		.memory = JPEG_MEM_FMT_PPLANAR
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV21M,
+		.encode = JPEG_ENCODE_H2V2,
+		.memory = JPEG_MEM_FMT_PPLANAR
+	}
+};
+
+static const struct qcom_jpeg_mal_boundary jpeg_mal_bounds[] = {
+	{ .bytes =   32, .boundary = JPEG_CFG_MAL_BOUND_32_BYTES  },
+	{ .bytes =   64, .boundary = JPEG_CFG_MAL_BOUND_64_BYTES  },
+	{ .bytes =  128, .boundary = JPEG_CFG_MAL_BOUND_128_BYTES },
+	{ .bytes =  256, .boundary = JPEG_CFG_MAL_BOUND_256_BYTES },
+	{ .bytes =  512, .boundary = JPEG_CFG_MAL_BOUND_512_BYTES },
+	{ .bytes = 1024, .boundary = JPEG_CFG_MAL_BOUND_1K_BYTES  },
+	{ .bytes = 4096, .boundary = JPEG_CFG_MAL_BOUND_4K_BYTES  }
+};
+
+static const struct qcom_jpeg_scale_blocks jpeg_mcu_blocks[] = {
+	[JPEG_ENCODE_H1V1] = {
+		.w_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+		.h_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+	},
+	[JPEG_ENCODE_H1V2] = {
+		.w_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8  },
+		.h_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+	},
+	[JPEG_ENCODE_H2V1] = {
+		.w_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+		.h_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8  },
+	},
+	[JPEG_ENCODE_H2V2] = {
+		.w_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+		.h_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+	},
+	[JPEG_ENCODE_MONO] = {
+		.w_block = { JPEG_MCU_BLOCK_8 },
+		.h_block = { JPEG_MCU_BLOCK_8 }
+	},
+};
+
+static inline int jpeg_get_memory_fmt(u32 fourcc)
+{
+	u32 fi;
+
+	for (fi = 0; fi < ARRAY_SIZE(jpeg_encode_fmt); fi++) {
+		if (jpeg_encode_fmt[fi].fourcc == fourcc)
+			return jpeg_encode_fmt[fi].memory;
+	}
+
+	return -EINVAL;
+}
+
+static inline int jpeg_get_encode_fmt(u32 fourcc)
+{
+	u32 fi;
+
+	for (fi = 0; fi < ARRAY_SIZE(jpeg_encode_fmt); fi++) {
+		if (jpeg_encode_fmt[fi].fourcc == fourcc)
+			return jpeg_encode_fmt[fi].encode;
+	}
+
+	return -EINVAL;
+}
+
+static inline int jpeg_get_mal_boundary(u32 width, const struct qcom_jpeg_mal_boundary *table,
+					u32 count)
+{
+	u32 bi;
+
+	if (WARN_ON_ONCE(!table || !count))
+		return -EINVAL;
+
+	for (bi = 0; bi < count; bi++) {
+		if (table[bi].bytes > width)
+			break;
+	}
+
+	if (!bi)
+		return table[0].boundary;
+
+	if (bi >= count)
+		return table[count - 1].boundary;
+
+	return table[bi - 1].boundary;
+}
+
+static inline u8 jpeg_get_mcu_per_block(u32 src_size, u32 dst_size)
+{
+	u8 h_rto;
+
+	if (WARN_ON_ONCE(!src_size || !dst_size))
+		return 0;
+
+	/* Calculate scale factor */
+	h_rto = max(src_size, dst_size) / min(src_size, dst_size);
+
+	if (h_rto < 2)
+		return jpeg_mcu_per_ratio[0];
+	if (h_rto < 4)
+		return jpeg_mcu_per_ratio[1];
+	if (h_rto < 8)
+		return jpeg_mcu_per_ratio[2];
+	if (h_rto < 16)
+		return jpeg_mcu_per_ratio[3];
+
+	return jpeg_mcu_per_ratio[4];
+}
+
+static inline int jpeg_get_mcu_geometry(enum qcom_jpeg_encode_fmt fmt, u32 width, u32 height,
+					u32 *blk_w, u32 *blk_h, u32 *mcu_cols, u32 *mcu_rows)
+{
+	const struct qcom_jpeg_scale_blocks *blks;
+	u32 bw = 0, bh = 0;
+	u8 pln;
+
+	/*
+	 * Dimensions are validated and normalized through V4L2 format negotiation;
+	 * this is a defensive guard for unexpected internal callers.
+	 */
+	if (WARN_ON_ONCE(!width || !height))
+		return -EINVAL;
+
+	blks = &jpeg_mcu_blocks[fmt];
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+		bw = max(bw, blks->w_block[pln]);
+		bh = max(bh, blks->h_block[pln]);
+	}
+
+	if (!bw || !bh)
+		return -EINVAL;
+
+	if (blk_w)
+		*blk_w = bw;
+	if (blk_h)
+		*blk_h = bh;
+
+	if (mcu_cols)
+		*mcu_cols = ALIGN(width, bw) / bw;
+
+	if (mcu_rows)
+		*mcu_rows = ALIGN(height, bh) / bh;
+
+	return 0;
+}
+
+/* Integer part of scale */
+static inline s32 jpeg_calc_scale_int(u32 in_width, u32 out_width)
+{
+	if (WARN_ON_ONCE(!out_width))
+		return 0;
+
+	return (s32)(in_width / out_width);
+}
+
+/* Fractional part of scale */
+static inline u32 jpeg_calc_scale_frac(u32 in_width, u32 out_width)
+{
+	u32 remainder;
+
+	if (WARN_ON_ONCE(!out_width))
+		return 0;
+
+	remainder = in_width % out_width;
+
+	/* 64-bit to avoid overflow during shift */
+	return (u32)(((u64)remainder << JPEG_Q5_21_SHIFT) / out_width);
+}
+
+static inline s32 jpeg_calc_q5_21(s32 int_part, u32 frac_part)
+{
+	return ((s32)((u32)int_part << JPEG_Q5_21_SHIFT)) | (frac_part & ((1u << 21) - 1));
+}
+
+static inline u32 jpeg_io_read(struct qcom_jenc_dev *jenc, u32 offset)
+{
+	return readl(jenc->jpeg_base + offset);
+}
+
+static inline void jpeg_io_write(struct qcom_jenc_dev *jenc, u32 offset, u32 value)
+{
+	writel(value, jenc->jpeg_base + offset);
+}
+
+/*
+ * Runtime bitfield helpers (for non-constant masks).
+ *
+ * Requirements:
+ *  - mask must be non-zero
+ *  - mask must be contiguous (e.g. 0x7u << n)
+ */
+
+static inline u32 jpeg_bits_get(u32 mask, u32 val)
+{
+	/* __ffs(0) is undefined; fail-safe on invalid masks. */
+	if (WARN_ON_ONCE(!mask))
+		return 0;
+
+	return (val & mask) >> __ffs(mask);
+}
+
+static inline u32 jpeg_bits_set(u32 mask, u32 val)
+{
+	/* __ffs(0) is undefined; fail-safe on invalid masks. */
+	if (WARN_ON_ONCE(!mask))
+		return 0;
+
+	return (val << __ffs(mask)) & mask;
+}
+
+static inline u32 jpeg_rd_bits(struct qcom_jenc_dev *jenc, u32 offs, enum qcom_jpeg_mask_id mid)
+{
+	u32 reg  = jpeg_io_read(jenc, offs);
+	u32 mask = jenc->res->hw_mask[mid];
+
+	return jpeg_bits_get(mask, reg);
+}
+
+/*
+ * Read-modify-write (for R/W registers)
+ */
+static inline void jpeg_rw_bits(struct qcom_jenc_dev *jenc, u32 offs, enum qcom_jpeg_mask_id mid,
+				u32 val)
+{
+	u32 reg  = jpeg_io_read(jenc, offs);
+	u32 mask = jenc->res->hw_mask[mid];
+
+	reg &= ~mask;
+	reg |= jpeg_bits_set(mask, val);
+
+	jpeg_io_write(jenc, offs, reg);
+}
+
+/*
+ * Write-only variant (for write only registers)
+ */
+static inline void jpeg_wo_bits(struct qcom_jenc_dev *jenc, u32 offs, enum qcom_jpeg_mask_id mid,
+				u32 val)
+{
+	u32 mask = jenc->res->hw_mask[mid];
+
+	jpeg_io_write(jenc, offs, jpeg_bits_set(mask, val));
+}
+
+static u8 jpeg_calculate_dqt(struct jenc_context *ectx, u8 dqt_value)
+{
+	u64 ratio;
+	u8 calc_val;
+
+	ratio = (QCOM_JPEG_QUALITY_MAX - ectx->quality_requested) << JPEG_DQT_SHIFT;
+	ratio = max_t(u64, 1, ratio);
+	do_div(ratio, QCOM_JPEG_QUALITY_MID);
+
+	calc_val = DIV64_U64_ROUND_CLOSEST(ratio * dqt_value, 1LU << JPEG_DQT_SHIFT);
+
+	return max_t(u8, 1, calc_val);
+}
+
+/*
+ * jpeg_update_dqt_cache - compute scaled DQT coefficients and store them in
+ * the software JPEG header cache (hdr_cache).  Safe to call from buf_prepare
+ * before the hardware is powered on; no MMIO access is performed here.
+ */
+static void jpeg_update_dqt_cache(struct jenc_context *ectx)
+{
+	u8 *base;
+	u8 dqt_val, idx;
+	int i;
+
+	/* Luma DQT cache update */
+	if (ectx->hdr_cache.dqt_one_offs) {
+		base = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_one_offs + 1];
+		for (i = 0; i < ARRAY_SIZE(t81k1_dct_luma_table); i++) {
+			dqt_val = jpeg_calculate_dqt(ectx, t81k1_dct_luma_table[i]);
+			idx = t81a6_dct_zig_zag_table[i];
+			base[idx] = dqt_val;
+		}
+	}
+
+	/* Chroma DQT cache update */
+	if (ectx->hdr_cache.dqt_two_offs) {
+		base = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_two_offs + 1];
+		for (i = 0; i < ARRAY_SIZE(t81k2_dct_chroma_table); i++) {
+			dqt_val = jpeg_calculate_dqt(ectx, t81k2_dct_chroma_table[i]);
+			idx = t81a6_dct_zig_zag_table[i];
+			base[idx] = dqt_val;
+		}
+	}
+}
+
+/*
+ * jpeg_upload_dmi_table - write the scaled DQT coefficients to the hardware
+ * DMI registers.  Must only be called from the job execution path where
+ * runtime PM has already been acquired (pm_runtime_resume_and_get).
+ *
+ * Reads precomputed values from hdr_cache (populated by jpeg_update_dqt_cache)
+ * to avoid redundant per-coefficient recalculation on the hot encode path.
+ */
+static void jpeg_upload_dmi_table(struct jenc_context *ectx)
+{
+	const struct qcom_jpeg_reg_offs *offs = ectx->jenc->res->hw_offs;
+	const u8 *luma_qt = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_one_offs + 1];
+	u32 pcfg = { 0x00000011 };
+	u32 addr = { 0x00000000 };
+	u32 reg_val;
+	int i;
+
+	/* DMI upload start sequence */
+	jpeg_io_write(ectx->jenc, offs->dmi_addr, addr);
+	jpeg_io_write(ectx->jenc, offs->dmi_cfg, pcfg);
+
+	/* DMI Luma upload — values are stored in zigzag order in hdr_cache */
+	for (i = 0; i < ARRAY_SIZE(t81k1_dct_luma_table); i++) {
+		reg_val = div_u64(U16_MAX + 1U, luma_qt[i]);
+		reg_val = clamp_t(u32, reg_val, 0, U16_MAX);
+		jpeg_io_write(ectx->jenc, offs->dmi_data, reg_val);
+	}
+
+	/* DMI Chroma upload — only present for color formats */
+	if (ectx->hdr_cache.dqt_two_offs) {
+		const u8 *chroma_qt = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_two_offs + 1];
+
+		for (i = 0; i < ARRAY_SIZE(t81k2_dct_chroma_table); i++) {
+			reg_val = div_u64(U16_MAX + 1U, chroma_qt[i]);
+			reg_val = clamp_t(u32, reg_val, 0, U16_MAX);
+			jpeg_io_write(ectx->jenc, offs->dmi_data, reg_val);
+		}
+	}
+
+	/* DMI upload end sequence */
+	jpeg_io_write(ectx->jenc, offs->dmi_cfg, addr);
+
+	ectx->quality_programmed = ectx->quality_requested;
+
+	dev_dbg(ectx->dev, "%s: ctx=%p quality_programmed=%d\n", __func__, ectx,
+		ectx->quality_programmed);
+}
+
+static void jpeg_sync_sg(struct device *dev,
+			 struct qcom_jpeg_buff *frame,
+			 enum dma_data_direction direction, bool for_device)
+{
+	u8 pln;
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+		struct sg_table *sgt = frame->plns[pln].sgt;
+
+		if (!frame->plns[pln].dma || !sgt)
+			break;
+
+		if (for_device)
+			dma_sync_sgtable_for_device(dev, sgt, direction);
+		else
+			dma_sync_sgtable_for_cpu(dev, sgt, direction);
+	}
+}
+
+static int jpeg_init(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_jpeg_reg_offs *offs;
+	unsigned long rtime;
+	u32 hw_ver;
+
+	if (!jenc || !jenc->dev || !jenc->jpeg_base || !jenc->res->hw_offs) {
+		pr_err("encoder HW init failed\n");
+		return -EINVAL;
+	}
+
+	offs	 = jenc->res->hw_offs;
+
+	jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+	jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_RESET_ACK, JPEG_SET_U32);
+
+	reinit_completion(&jenc->reset_complete);
+
+	jpeg_wo_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_COMMON, JPEG_SET_U32);
+
+	rtime = wait_for_completion_timeout(&jenc->reset_complete,
+					    msecs_to_jiffies(JPEG_RESET_TIMEOUT_MS));
+	if (!rtime) {
+		dev_err(jenc->dev, "encoder HW reset timeout\n");
+		disable_irq(jenc->irq);
+		return -ETIME;
+	}
+
+	hw_ver = jpeg_io_read(jenc, offs->hw_version);
+	dev_dbg(jenc->dev, "JPEG HW encoder version %d.%d.%d\n",
+		jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_VER_MAJOR], hw_ver),
+		jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_VER_MINOR], hw_ver),
+		jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_VER_STEP], hw_ver));
+
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_RD_PLNS_QUEUE, JPEG_SET_U32);
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_RD_PLNS_QUEUE, JPEG_CLR_U32);
+
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_WR_PLNS_QUEUE, JPEG_SET_U32);
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_WR_PLNS_QUEUE, JPEG_CLR_U32);
+
+	jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+	jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+
+	return 0;
+}
+
+static int jpeg_exec(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_HW_START, 1);
+
+	return 0;
+}
+
+static void jpeg_stop(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_HW_START, 0);
+
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_RD_PLNS_QUEUE, JPEG_SET_U32);
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_RD_PLNS_QUEUE, JPEG_CLR_U32);
+
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_WR_PLNS_QUEUE, JPEG_SET_U32);
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_CLR_WR_PLNS_QUEUE, JPEG_CLR_U32);
+
+	jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+	jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+}
+
+static int jpeg_deinit(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	unsigned long rtime;
+
+	jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+	jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_STOP_ACK, JPEG_SET_U32);
+
+	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_HW_STOP, 1);
+
+	reinit_completion(&jenc->stop_complete);
+	rtime = wait_for_completion_timeout(&jenc->stop_complete,
+					    msecs_to_jiffies(JPEG_STOP_TIMEOUT_MS));
+	if (!rtime) {
+		dev_err(jenc->dev, "encoder HW stop timeout\n");
+		return -ETIME;
+	}
+
+	jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_ALL_BITS, JPEG_CLR_U32);
+	jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
+
+	return 0;
+}
+
+static int jpeg_apply_fe_addr(struct jenc_context *ectx, struct qcom_jenc_queue *q,
+			      struct vb2_buffer *vb)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	struct qcom_jpeg_buff *frame = &q->buff[vb->index];
+	struct v4l2_pix_format_mplane *fmt = &q->vf;
+	unsigned long flags;
+	u8 pln = 0;
+
+	if (WARN_ON_ONCE(!frame->plns[pln].dma))
+		return -EPERM;
+
+	jpeg_sync_sg(jenc->dev, frame, DMA_TO_DEVICE, true);
+
+	for (pln = 0; pln < fmt->num_planes; pln++) {
+		if (!frame->plns[pln].sgt || !frame->plns[pln].sgt->sgl)
+			break;
+
+		jpeg_io_write(jenc, offs->fe.pntr[pln], frame->plns[pln].dma);
+		jpeg_io_write(jenc, offs->fe.offs[pln], 0);
+
+		dev_dbg(jenc->dev, "%s: pln=%d addr=0x%llx idx:%d\n", __func__,
+			pln, frame->plns[pln].dma, vb->index);
+	}
+
+	spin_lock_irqsave(&jenc->hw_lock, flags);
+	q->buff_id = vb->index;
+	spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+	return 0;
+}
+
+static int jpeg_store_fe_next(struct jenc_context *ectx, struct vb2_buffer *vb2)
+{
+	struct qcom_jenc_queue *q = &ectx->bufq[TYPE2QID(vb2->type)];
+	struct qcom_jpeg_buff *buff = &q->buff[vb2->index];
+	u8 pln = 0;
+
+	buff->plns[pln].sgt = vb2_dma_sg_plane_desc(vb2, pln);
+	if (!buff->plns[pln].sgt)
+		return -EINVAL;
+
+	if (!buff->plns[pln].sgt->sgl)
+		return -EINVAL;
+
+	buff->plns[pln].dma = sg_dma_address(buff->plns[pln].sgt->sgl);
+	if (!buff->plns[pln].dma)
+		return -EINVAL;
+
+	buff->plns[pln].size = vb2_plane_size(vb2, pln);
+	if (!buff->plns[pln].size)
+		return -EINVAL;
+
+	for (pln = 1; pln < q->vf.num_planes; pln++) {
+		buff->plns[pln].sgt = vb2_dma_sg_plane_desc(vb2, pln);
+		if (!buff->plns[pln].sgt || !buff->plns[pln].sgt->sgl)
+			return -EINVAL;
+
+		buff->plns[pln].dma = sg_dma_address(buff->plns[pln].sgt->sgl);
+		if (!buff->plns[pln].dma)
+			return -EINVAL;
+
+		buff->plns[pln].size = vb2_plane_size(vb2, pln);
+		if (!buff->plns[pln].size)
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int jpeg_setup_fe_size(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	struct v4l2_pix_format_mplane *sfmt = &q->vf;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	u8 pln;
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+		jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_WIDTH, 0);
+		jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_HEIGHT, 0);
+		jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_STRIDE, 0);
+	}
+
+	for (pln = 0; pln < sfmt->num_planes; pln++) {
+		jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_WIDTH,
+			     sfmt->width  - 1);
+		jpeg_rw_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_HEIGHT,
+			     sfmt->height  - 1);
+		jpeg_rw_bits(jenc, offs->fe.stride[pln], JMSK_PLNS_RD_STRIDE,
+			     sfmt->plane_fmt[pln].bytesperline);
+
+		dev_dbg(ectx->dev, "%s: ctx=%p pln=%d width=%d height=%d stride=%d\n",
+			__func__, ectx, pln,
+			jpeg_rd_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_WIDTH),
+			jpeg_rd_bits(jenc, offs->fe.bsize[pln], JMSK_PLNS_RD_BUF_SIZE_HEIGHT),
+			jpeg_rd_bits(jenc, offs->fe.stride[pln], JMSK_PLNS_RD_STRIDE));
+	}
+
+	return 0;
+}
+
+static int jpeg_setup_fe_hinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	u8 pln;
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
+		jpeg_io_write(jenc, offs->fe.hinit[pln], 0);
+
+	return 0;
+}
+
+static int jpeg_setup_fe_vinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	u8 pln;
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
+		jpeg_io_write(jenc, offs->fe.vinit[pln], 0);
+
+	return 0;
+}
+
+static int jpeg_setup_fe_params(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	struct v4l2_pix_format_mplane *sfmt = &q->vf;
+	struct v4l2_pix_format_mplane *dfmt = &ectx->bufq[JENC_DST_QUEUE].vf;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	u8 expected_planes, pln;
+	int rval;
+
+	jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MAL_EN, 1);
+	jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_BOTTOM_VPAD_EN, 1);
+
+	rval = jpeg_get_memory_fmt(sfmt->pixelformat);
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: invalid memory format for v4l2 format:0x%x\n",
+			__func__, sfmt->pixelformat);
+		return -EINVAL;
+	}
+
+	switch (rval) {
+	case JPEG_MEM_FMT_MONO:
+		expected_planes = 1;
+		break;
+	case JPEG_MEM_FMT_PPLANAR:
+		expected_planes = 2;
+		break;
+	case JPEG_MEM_FMT_PLANAR:
+		expected_planes = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (sfmt->num_planes != expected_planes) {
+		dev_err(ectx->dev, "plane mismatch fmt=%u expected=%u got=%u\n",
+			rval, expected_planes, sfmt->num_planes);
+		return -EINVAL;
+	}
+
+	jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MEMORY_FORMAT, rval);
+
+	jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN0_EN, 0);
+	jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN1_EN, 0);
+	jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN2_EN, 0);
+
+	if (sfmt->width == dfmt->width && sfmt->height == dfmt->height) {
+		/* No scaling */
+		jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_SIXTEEN_MCU_EN, 1);
+		jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MCUS_PER_BLOCK, 0);
+	} else {
+		u8 mcu_per_blks;
+
+		/* Scaling */
+		jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_SIXTEEN_MCU_EN, 0);
+		/* get value according to image width */
+		mcu_per_blks = jpeg_get_mcu_per_block(sfmt->width, dfmt->width);
+		/* get value according to image height assign the bigger */
+		mcu_per_blks = max_t(u8, mcu_per_blks,
+				     jpeg_get_mcu_per_block(sfmt->height, dfmt->height));
+
+		jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MCUS_PER_BLOCK, mcu_per_blks);
+	}
+
+	dev_dbg(ectx->dev, "%s: sixteen MCU enabled=%d, %d MCU per blocks\n", __func__,
+		jpeg_rd_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_SIXTEEN_MCU_EN),
+		jpeg_rd_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MCUS_PER_BLOCK));
+
+	rval = jpeg_get_mal_boundary(sfmt->width, jpeg_mal_bounds, ARRAY_SIZE(jpeg_mal_bounds));
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: failed to get FE mal boundary width=%u\n", __func__,
+			sfmt->width);
+		return -EINVAL;
+	}
+	jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MAL_BOUNDARY, rval);
+
+	dev_dbg(ectx->dev, "%s: optimal FE mal boundary=%d\n", __func__,
+		jpeg_rd_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MAL_BOUNDARY));
+
+	rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n",
+			__func__, sfmt->pixelformat);
+		return -EINVAL;
+	}
+
+	switch (rval) {
+	case JPEG_ENCODE_MONO:
+	case JPEG_ENCODE_H1V1:
+	case JPEG_ENCODE_H2V1:
+		jpeg_rw_bits(jenc, offs->fe.vbpad_cfg, JMSK_FE_VBPAD_CFG_BLOCK_ROW,
+			     DIV_ROUND_UP(sfmt->height, JPEG_MCU_BLOCK_8));
+		break;
+	case JPEG_ENCODE_H1V2:
+	case JPEG_ENCODE_H2V2:
+		jpeg_rw_bits(jenc, offs->fe.vbpad_cfg, JMSK_FE_VBPAD_CFG_BLOCK_ROW,
+			     DIV_ROUND_UP(sfmt->height, JPEG_MCU_BLOCK_16));
+		break;
+	default:
+		dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n", __func__, rval);
+		return -EINVAL;
+	}
+
+	dev_dbg(ectx->dev, "%s: FE vpad config=%d\n", __func__,
+		jpeg_rd_bits(jenc, offs->fe.vbpad_cfg, JMSK_FE_VBPAD_CFG_BLOCK_ROW));
+
+	if (sfmt->pixelformat == V4L2_PIX_FMT_NV21M || sfmt->pixelformat == V4L2_PIX_FMT_NV61M)
+		jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_CBCR_ORDER, 1);
+	else
+		jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_CBCR_ORDER, 0);
+
+	for (pln = 0; pln < sfmt->num_planes; pln++) {
+		if (sfmt->width && sfmt->height) {
+			switch (pln) {
+			case 0:
+				jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN0_EN, 1);
+				break;
+			case 1:
+				jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN1_EN, 1);
+				break;
+			case 2:
+				jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_PLN2_EN, 1);
+				break;
+			}
+		}
+	}
+
+	jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_FE_ENABLE, 1);
+
+	return 0;
+}
+
+static int jpeg_setup_fe(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	int rc;
+
+	rc = jpeg_setup_fe_size(ectx, q);
+	if (rc)
+		return rc;
+
+	rc = jpeg_setup_fe_hinit(ectx, q);
+	if (rc)
+		return rc;
+
+	rc = jpeg_setup_fe_vinit(ectx, q);
+	if (rc)
+		return rc;
+
+	rc = jpeg_setup_fe_params(ectx, q);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static int jpeg_ensure_header_cache(struct jenc_context *ectx)
+{
+	struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+	int rc;
+
+	if (ectx->hdr_cache.size)
+		return 0;
+
+	rc = qcom_jenc_header_init(&ectx->hdr_cache, sq->vf.pixelformat);
+	if (rc) {
+		dev_err(ectx->dev, "JFIF header lazy init failed\n");
+		return rc;
+	}
+
+	return 0;
+}
+
+static int jpeg_apply_we_addr(struct jenc_context *ectx, struct qcom_jenc_queue *q,
+			      struct vb2_buffer *vb)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	struct qcom_jpeg_buff *frame = &q->buff[vb->index];
+	void *mptr = vb2_plane_vaddr(vb, 0);
+	dma_addr_t dma = frame->plns[0].dma;
+	unsigned long flags;
+	int rc;
+	u8 pln = 0;
+
+	if (WARN_ON_ONCE(!dma))
+		return -EPERM;
+
+	if (WARN_ON_ONCE(!mptr))
+		return -EPERM;
+
+	rc = jpeg_ensure_header_cache(ectx);
+	if (rc)
+		return rc;
+
+	/*
+	 * Under quality_mutex: force a DQT refresh if the header was just
+	 * (re)created (quality_programmed == 0) or if quality changed since
+	 * the last frame.  Both the cache update and the HW DMI upload are
+	 * done here so that hdr_cache and the hardware are always in sync
+	 * before jpeg_exec() fires.
+	 */
+	mutex_lock(&ectx->quality_mutex);
+	if (!ectx->hdr_cache.size || ectx->quality_programmed != ectx->quality_requested) {
+		jpeg_update_dqt_cache(ectx);
+		jpeg_upload_dmi_table(ectx);
+	}
+	mutex_unlock(&ectx->quality_mutex);
+
+	/*
+	 * Invalidate stale CPU cache lines before writing the JPEG header
+	 * with the CPU into the destination buffer.
+	 */
+	jpeg_sync_sg(jenc->dev, frame, DMA_BIDIRECTIONAL, false);
+
+	dma += qcom_jenc_header_emit(&ectx->hdr_cache, mptr,
+				     min_t(size_t, vb->planes[0].length, ectx->hdr_cache.size),
+				     q->vf.width, q->vf.height);
+	qcom_jenc_dqts_emit(&ectx->hdr_cache, mptr);
+
+	/*
+	 * Flush CPU writes to the header before handing the buffer to the
+	 * hardware DMA engine.
+	 */
+	jpeg_sync_sg(jenc->dev, frame, DMA_BIDIRECTIONAL, true);
+
+	jpeg_io_write(jenc, offs->we.pntr[pln], dma);
+
+	dev_dbg(jenc->dev, "%s: pln=%d addr=0x%llx idx:%d\n", __func__,
+		pln, dma, vb->index);
+
+	spin_lock_irqsave(&jenc->hw_lock, flags);
+	q->buff_id = vb->index;
+	spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+	return 0;
+}
+
+static int jpeg_store_we_next(struct jenc_context *ectx, struct vb2_buffer *vb2)
+{
+	struct qcom_jenc_queue *q = &ectx->bufq[TYPE2QID(vb2->type)];
+	struct qcom_jpeg_buff *frame = &q->buff[vb2->index];
+	struct sg_table *sgt;
+	dma_addr_t dma;
+
+	sgt = vb2_dma_sg_plane_desc(vb2, 0);
+	if (!sgt || !sgt->sgl)
+		return -EINVAL;
+
+	dma = sg_dma_address(sgt->sgl);
+	if (!dma)
+		return -EINVAL;
+
+	if (!vb2_plane_vaddr(vb2, 0))
+		return -EINVAL;
+
+	frame->plns[0].sgt = sgt;
+	frame->plns[0].dma = dma;
+	frame->plns[0].size = vb2_plane_size(vb2, 0);
+
+	return 0;
+}
+
+static int jpeg_setup_we_size(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	struct v4l2_pix_format_mplane *dfmt = &q->vf;
+	u8 pln;
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++)
+		jpeg_rw_bits(jenc, offs->we.stride[pln], JMSK_PLNS_WR_STRIDE, 0);
+
+	jpeg_io_write(jenc, offs->we.bsize[0], dfmt->plane_fmt[0].sizeimage);
+
+	dev_dbg(ectx->dev, "%s: ctx=%p size=%u\n", __func__,
+		ectx, dfmt->plane_fmt[0].sizeimage);
+
+	return 0;
+}
+
+static int jpeg_setup_we_hinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	struct v4l2_pix_format_mplane *dfmt = &q->vf;
+	u8 pln;
+
+	if (!dfmt->width) {
+		dev_err(ectx->dev, "%s: invalid destination width=%d\n", __func__, dfmt->width);
+		return -EINVAL;
+	}
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+		jpeg_rw_bits(jenc, offs->we.hinit[pln], JMSK_PLNS_WR_HINIT, 0);
+		jpeg_rw_bits(jenc, offs->we.hstep[pln], JMSK_PLNS_WR_HSTEP, 0);
+	}
+
+	jpeg_rw_bits(jenc, offs->we.hstep[0], JMSK_PLNS_WR_HSTEP, dfmt->width);
+
+	dev_dbg(ectx->dev, "%s: ctx=%p hstep=%u\n", __func__, ectx,
+		jpeg_rd_bits(jenc, offs->we.hstep[0], JMSK_PLNS_WR_HSTEP));
+
+	return 0;
+}
+
+static int jpeg_setup_we_vinit(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	struct v4l2_pix_format_mplane *dfmt = &q->vf;
+	u8 pln;
+
+	if (!dfmt->height) {
+		dev_err(ectx->dev, "%s: invalid destination height=%d\n", __func__, dfmt->height);
+		return -EINVAL;
+	}
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+		jpeg_rw_bits(jenc, offs->we.vinit[pln], JMSK_PLNS_WR_VINIT, 0);
+		jpeg_rw_bits(jenc, offs->we.vstep[pln], JMSK_PLNS_WR_VSTEP, 0);
+	}
+
+	jpeg_rw_bits(jenc, offs->we.vstep[0], JMSK_PLNS_WR_VSTEP, dfmt->height);
+
+	dev_dbg(ectx->dev, "%s: ctx=%p vstep=%u\n", __func__, ectx,
+		jpeg_rd_bits(jenc, offs->we.vstep[0], JMSK_PLNS_WR_VSTEP));
+
+	return 0;
+}
+
+static int jpeg_setup_we_params(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	struct v4l2_pix_format_mplane *dfmt = &q->vf;
+	u32 blk_w, blk_h, mcu_cols, mcu_rows;
+	int rval;
+
+	rval = jpeg_get_memory_fmt(dfmt->pixelformat);
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: invalid memory format for v4l2 format:0x%x\n",
+			__func__, dfmt->pixelformat);
+		return -EINVAL;
+	}
+	jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_MEMORY_FORMAT, rval);
+
+	rval = jpeg_get_mal_boundary(dfmt->width, jpeg_mal_bounds, ARRAY_SIZE(jpeg_mal_bounds));
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: failed to get WE mal boundary width=%u\n",
+			__func__, dfmt->width);
+		return -EINVAL;
+	}
+	jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_MAL_BOUNDARY, rval);
+
+	dev_dbg(ectx->dev, "%s: optimal WE mal boundary=%d\n", __func__,
+		jpeg_rd_bits(jenc, offs->we_cfg, JMSK_WE_CFG_MAL_BOUNDARY));
+
+	rval = jpeg_get_encode_fmt(dfmt->pixelformat);
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n",
+			__func__, dfmt->pixelformat);
+		return rval;
+	}
+
+	rval = jpeg_get_mcu_geometry(rval, dfmt->width, dfmt->height, &blk_w, &blk_h,
+				     &mcu_cols, &mcu_rows);
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: invalid MCU geometry mcu_cols=%d mcu_rows=%d\n",
+			__func__, mcu_cols, mcu_rows);
+		return rval;
+	}
+
+	dev_dbg(ectx->dev, "%s blk_w=%u blk_h=%u cols=%u rows=%u\n", __func__,
+		blk_w, blk_h, mcu_cols, mcu_rows);
+
+	jpeg_rw_bits(jenc, offs->we.blocks[0], JMSK_PLNS_WR_BLOCK_CFG_PER_RAW, mcu_rows - 1);
+	jpeg_rw_bits(jenc, offs->we.blocks[0], JMSK_PLNS_WR_BLOCK_CFG_PER_COL, mcu_cols - 1);
+
+	jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_CBCR_ORDER, 1);
+	jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_MAL_EN, 1);
+	jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_POP_BUFF_ON_EOS, 1);
+	jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_PLN0_EN, 1);
+
+	jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_MODE, 1);
+	jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_WE_ENABLE, 1);
+
+	return 0;
+}
+
+static int jpeg_setup_we(struct jenc_context *ectx, struct qcom_jenc_queue *q)
+{
+	int rc;
+
+	rc = jpeg_setup_we_size(ectx, q);
+	if (rc)
+		return rc;
+
+	rc = jpeg_setup_we_hinit(ectx, q);
+	if (rc)
+		return rc;
+
+	rc = jpeg_setup_we_vinit(ectx, q);
+	if (rc)
+		return rc;
+
+	return jpeg_setup_we_params(ectx, q);
+}
+
+static int jpeg_setup_scale(struct jenc_context *ectx)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+	struct qcom_jenc_queue *dq = &ectx->bufq[JENC_DST_QUEUE];
+	struct v4l2_pix_format_mplane *sfmt = &sq->vf;
+	struct v4l2_pix_format_mplane *dfmt = &dq->vf;
+	u32 blk_w, blk_h, mcu_cols, mcu_rows;
+	int rval;
+	u8 pln;
+
+	jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_SCALE_RESET, 1);
+
+	/* explicit no scaling */
+	jpeg_rw_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_HSCALE_ENABLE, 0);
+	jpeg_rw_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_VSCALE_ENABLE, 0);
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+		jpeg_io_write(jenc, offs->scale.hstep[pln], JPEG_DEFAULT_SCALE_STEP);
+		jpeg_io_write(jenc, offs->scale.vstep[pln], JPEG_DEFAULT_SCALE_STEP);
+	}
+
+	if (jpeg_rd_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_HSCALE_ENABLE)) {
+		for (pln = 0; pln < sq->vf.num_planes; pln++) {
+			jpeg_rw_bits(jenc, offs->scale.hstep[pln],
+				     JMSK_SCALE_PLNS_HSTEP_INTEGER,
+				     jpeg_calc_scale_int(sfmt->width, dfmt->width));
+			jpeg_rw_bits(jenc, offs->scale.hstep[pln],
+				     JMSK_SCALE_PLNS_HSTEP_FRACTIONAL,
+				     jpeg_calc_scale_frac(sfmt->width, dfmt->width));
+
+			dev_dbg(ectx->dev, "%s: ctx=%p hint=%d hfrac=%d\n",
+				__func__, ectx,
+				jpeg_rd_bits(jenc, offs->scale.hstep[pln],
+					     JMSK_SCALE_PLNS_HSTEP_INTEGER),
+				jpeg_rd_bits(jenc, offs->scale.hstep[pln],
+					     JMSK_SCALE_PLNS_HSTEP_FRACTIONAL));
+		}
+	}
+
+	if (jpeg_rd_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_VSCALE_ENABLE)) {
+		for (pln = 0; pln < sq->vf.num_planes; pln++) {
+			jpeg_rw_bits(jenc, offs->scale.vstep[pln],
+				     JMSK_SCALE_PLNS_VSTEP_INTEGER,
+				     jpeg_calc_scale_int(sfmt->height, dfmt->height));
+			jpeg_rw_bits(jenc, offs->scale.vstep[pln],
+				     JMSK_SCALE_PLNS_VSTEP_FRACTIONAL,
+				     jpeg_calc_scale_frac(sfmt->height, dfmt->height));
+
+			dev_dbg(ectx->dev, "%s: ctx=%p vint=%d vfrac=%d\n",
+				__func__, ectx,
+				jpeg_rd_bits(jenc, offs->scale.vstep[pln],
+					     JMSK_SCALE_PLNS_VSTEP_INTEGER),
+				jpeg_rd_bits(jenc, offs->scale.vstep[pln],
+					     JMSK_SCALE_PLNS_VSTEP_FRACTIONAL));
+		}
+	}
+
+	rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n",
+			__func__, sfmt->pixelformat);
+		return -EINVAL;
+	}
+
+	rval = jpeg_get_mcu_geometry(rval, dfmt->width, dfmt->height, &blk_w, &blk_h,
+				     &mcu_cols, &mcu_rows);
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: invalid MCU geometry blk_w=%d blk_h=%d\n",
+			__func__, blk_w, blk_h);
+		return -EINVAL;
+	}
+
+	dev_dbg(ectx->dev, "%s blk_w=%u blk_h=%u cols=%u rows=%u\n", __func__, blk_w, blk_h,
+		mcu_cols, mcu_rows);
+
+	for (pln = 0; pln < sq->vf.num_planes; pln++) {
+		jpeg_rw_bits(jenc, offs->scale_out_cfg[pln],
+			     JMSK_SCALE_PLNS_OUT_CFG_BLK_WIDTH, mcu_cols - 1);
+		jpeg_rw_bits(jenc, offs->scale_out_cfg[pln],
+			     JMSK_SCALE_PLNS_OUT_CFG_BLK_HEIGHT, mcu_rows - 1);
+	}
+
+	dev_dbg(ectx->dev, "%s: ctx=%p scale src=%ux%u dst=%ux%u enable=%d/%d\n",
+		__func__, ectx, sfmt->width, sfmt->height, dfmt->width, dfmt->height,
+		jpeg_rd_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_HSCALE_ENABLE),
+		jpeg_rd_bits(jenc, offs->scale_cfg, JMSK_SCALE_CFG_VSCALE_ENABLE));
+
+	/* Disabled, but must be configured */
+	jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_SCALE_ENABLE, 0);
+
+	return 0;
+}
+
+static int jpeg_setup_encode(struct jenc_context *ectx)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+	struct v4l2_pix_format_mplane *sfmt = &sq->vf;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	u32 blk_w, blk_h, mcu_cols, mcu_rows;
+	int rval;
+
+	if (!sfmt->width || !sfmt->height)
+		return -EINVAL;
+
+	jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_ENCODER_RESET, 1);
+
+	rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: unsupported encode format fourcc=0x%x\n",
+			__func__, sfmt->pixelformat);
+		return -EINVAL;
+	}
+	jpeg_rw_bits(jenc, offs->enc_cfg, JMSK_ENC_CFG_IMAGE_FORMAT, rval);
+
+	rval = jpeg_get_mcu_geometry(rval, sfmt->width, sfmt->height, &blk_w, &blk_h,
+				     &mcu_cols, &mcu_rows);
+	if (rval < 0) {
+		dev_err(ectx->dev, "%s: invalid MCU geometry mcu_cols=%d mcu_rows=%d\n",
+			__func__, mcu_cols, mcu_rows);
+		return -EINVAL;
+	}
+
+	dev_dbg(ectx->dev, "%s blk_w=%u blk_h=%u cols=%u rows=%u\n", __func__,
+		blk_w, blk_h, mcu_cols, mcu_rows);
+
+	jpeg_rw_bits(jenc, offs->enc_img_size, JMSK_ENC_IMAGE_SIZE_WIDTH, mcu_cols - 1);
+	jpeg_rw_bits(jenc, offs->enc_img_size, JMSK_ENC_IMAGE_SIZE_HEIGHT, mcu_rows - 1);
+
+	dev_dbg(ectx->dev, "%s: ctx=%p width=%d height=%d\n", __func__, ectx,
+		jpeg_rd_bits(jenc, offs->enc_img_size, JMSK_ENC_IMAGE_SIZE_WIDTH),
+		jpeg_rd_bits(jenc, offs->enc_img_size, JMSK_ENC_IMAGE_SIZE_HEIGHT));
+
+	jpeg_rw_bits(jenc, offs->enc_cfg, JMSK_ENC_CFG_APPLY_EOI, 1);
+	jpeg_rw_bits(jenc, offs->core_cfg, JMSK_CORE_CFG_ENC_ENABLE, 1);
+
+	return 0;
+}
+
+static irqreturn_t op_jpeg_irq_bot(int irq, void *data)
+{
+	struct qcom_jenc_dev *jenc = data;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	u32 irq_status;
+	u32 irq_mask;
+	unsigned long flags;
+
+	irq_status = READ_ONCE(jenc->pending_irq_status);
+
+	irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_SESSION_DONE];
+	if (jpeg_bits_get(irq_mask, irq_status)) {
+		struct jenc_context *ctx = jenc->actx;
+		struct qcom_jenc_queue *dq;
+		size_t out_size;
+
+		spin_lock_irqsave(&jenc->hw_lock, flags);
+		jenc->actx = NULL;
+		spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+		if (!ctx)
+			return IRQ_HANDLED;
+
+		dq = &ctx->bufq[JENC_DST_QUEUE];
+		if (dq->buff_id >= 0) {
+			struct qcom_jpeg_buff *frame;
+			unsigned long flags;
+
+			spin_lock_irqsave(&jenc->hw_lock, flags);
+			frame = &dq->buff[dq->buff_id];
+			out_size = jpeg_io_read(jenc, offs->enc_out_size);
+			spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+			dev_dbg(jenc->dev, "complete idx:%d addr=0x%llx size=%zu\n",
+				dq->buff_id, frame->plns[0].dma, out_size);
+
+			jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_DONE,
+					    out_size + JPEG_HEADER_MAX);
+			jpeg_stop(jenc);
+		}
+	}
+
+	irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_SESSION_ERROR];
+	if (jpeg_bits_get(irq_mask, irq_status)) {
+		struct jenc_context *ctx = jenc->actx;
+
+		spin_lock_irqsave(&jenc->hw_lock, flags);
+		jenc->actx = NULL;
+		spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+		dev_err(jenc->dev, "encoder hardware failure=0x%x\n",
+			jpeg_bits_get(irq_mask, irq_status));
+		if (ctx)
+			jenc->enc_hw_irq_cb(ctx, VB2_BUF_STATE_ERROR, 0);
+
+		jpeg_stop(jenc);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t op_jpeg_irq_top(int irq, void *data)
+{
+	struct qcom_jenc_dev *jenc = data;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	u32 irq_status;
+	u32 irq_mask;
+	unsigned long flags;
+
+	spin_lock_irqsave(&jenc->hw_lock, flags);
+
+	irq_status = jpeg_io_read(jenc, offs->int_status);
+	jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, irq_status);
+
+	irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_RESET_ACK];
+	if (jpeg_bits_get(irq_mask, irq_status)) {
+		complete(&jenc->reset_complete);
+		spin_unlock_irqrestore(&jenc->hw_lock, flags);
+		return IRQ_HANDLED;
+	}
+
+	irq_mask = jenc->res->hw_mask[JMSK_IRQ_STATUS_STOP_ACK];
+	if (jpeg_bits_get(irq_mask, irq_status)) {
+		complete(&jenc->stop_complete);
+		dev_dbg(jenc->dev, "hardware stop acknowledged\n");
+		spin_unlock_irqrestore(&jenc->hw_lock, flags);
+		return IRQ_HANDLED;
+	}
+
+	WRITE_ONCE(jenc->pending_irq_status, irq_status);
+
+	spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+	return IRQ_WAKE_THREAD;
+}
+
+static void op_jpeg_get_hw_caps(struct qcom_jenc_dev *jenc, u32 *caps)
+{
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	u32 hw_caps;
+
+	hw_caps = jpeg_io_read(jenc, offs->hw_capability);
+	dev_dbg(jenc->dev, "CAPS: encode=%d decode=%d upscale=%d downscale=%d\n",
+		jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_CAP_ENCODE], hw_caps),
+		jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_CAP_DECODE], hw_caps),
+		jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_CAP_UPSCALE], hw_caps),
+		jpeg_bits_get(jenc->res->hw_mask[JMSK_HW_CAP_DOWNSCALE], hw_caps));
+
+	*caps = hw_caps;
+}
+
+static struct qcom_jenc_queue *op_jpeg_get_buff_queue(struct jenc_context *ectx,
+						      enum qcom_enc_qid id)
+{
+	return &ectx->bufq[id];
+}
+
+static int op_jpeg_queue_setup(struct jenc_context *ectx, enum qcom_enc_qid id)
+{
+	int rc;
+
+	if (id == JENC_SRC_QUEUE) {
+		struct qcom_jenc_queue *q = &ectx->bufq[id];
+
+		rc = qcom_jenc_header_init(&ectx->hdr_cache, q->vf.pixelformat);
+		if (rc) {
+			dev_err(ectx->dev, "JFIF header init failed\n");
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int op_jpeg_src_fmt_update(struct jenc_context *ectx, u32 old_fourcc, u32 new_fourcc)
+{
+	bool old_is_mono = (old_fourcc == V4L2_PIX_FMT_GREY);
+	bool new_is_mono = (new_fourcc == V4L2_PIX_FMT_GREY);
+	int rc;
+
+	/* Header layout changes only for mono <-> color source format switch. */
+	if (old_is_mono == new_is_mono)
+		return 0;
+
+	rc = qcom_jenc_header_init(&ectx->hdr_cache, new_fourcc);
+	if (rc) {
+		dev_err(ectx->dev, "JFIF header reinit failed\n");
+		return rc;
+	}
+
+	/* Force DQT upload after source profile switch. */
+	ectx->quality_programmed = 0;
+
+	return 0;
+}
+
+static int op_jpeg_buffer_prepare(struct jenc_context *ectx, struct vb2_buffer *vb2)
+{
+	int rc;
+
+	if (V4L2_TYPE_IS_OUTPUT(vb2->type)) {
+		rc = jpeg_store_fe_next(ectx, vb2);
+		if (rc)
+			dev_err(ectx->dev, "%s: cannot set up fetch addr\n", __func__);
+	} else {
+		rc = jpeg_store_we_next(ectx, vb2);
+		if (rc)
+			dev_err(ectx->dev, "%s: cannot set up write addr\n", __func__);
+	}
+
+	return rc;
+}
+
+static int op_jpeg_process_exec(struct qcom_jenc_dev *jenc, struct jenc_context *ectx,
+				struct vb2_buffer *vb)
+{
+	struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+	struct qcom_jenc_queue *dq = &ectx->bufq[JENC_DST_QUEUE];
+	unsigned long flags;
+	int rc;
+
+	spin_lock_irqsave(&jenc->hw_lock, flags);
+	jenc->actx = ectx;
+	spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+	if (V4L2_TYPE_IS_OUTPUT(vb->type)) {
+		rc = jpeg_setup_fe(ectx, sq);
+		if (rc)
+			goto err_clear_ctx;
+
+		rc = jpeg_apply_fe_addr(ectx, sq, vb);
+		if (rc)
+			goto err_clear_ctx;
+	} else {
+		rc = jpeg_setup_we(ectx, dq);
+		if (rc)
+			goto err_clear_ctx;
+
+		rc = jpeg_apply_we_addr(ectx, dq, vb);
+		if (rc)
+			goto err_clear_ctx;
+	}
+
+	if (sq->sequence == dq->sequence) {
+		rc = jpeg_setup_scale(ectx);
+		if (rc)
+			goto err_clear_ctx;
+
+		rc = jpeg_setup_encode(ectx);
+		if (rc)
+			goto err_clear_ctx;
+
+		jpeg_exec(jenc);
+	}
+
+	return 0;
+
+err_clear_ctx:
+	spin_lock_irqsave(&jenc->hw_lock, flags);
+	if (jenc->actx == ectx)
+		jenc->actx = NULL;
+	spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+	return rc;
+}
+
+static int op_jpeg_prepare(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+
+	jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_DECODER_RESET, 1);
+	jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_BLOCK_FORMATTER_RST, 1);
+	jpeg_rw_bits(jenc, offs->reset_cmd, JMSK_RST_CMD_CORE_RESET, 1);
+
+	return 0;
+}
+
+static enum qcom_soc_perf_level jpeg_select_perf_level(struct jenc_context *ectx)
+{
+	struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+	u64 pixels, load;
+	u32 quality;
+	u32 qscale;
+
+	if (!sq->vf.width || !sq->vf.height)
+		return QCOM_SOC_PERF_NOMINAL;
+
+	mutex_lock(&ectx->quality_mutex);
+	quality = ectx->quality_requested;
+	mutex_unlock(&ectx->quality_mutex);
+
+	/* Minimal v1 quality weighting: low/medium/high quality buckets. */
+	if (quality <= 70)
+		qscale = 90;
+	else if (quality <= 90)
+		qscale = 100;
+	else
+		qscale = 115;
+
+	pixels = (u64)sq->vf.width * sq->vf.height;
+	load = pixels * qscale;
+
+	/* Keep a conservative floor for active encode sessions. */
+	if (load <= (u64)1920 * 1088 * 100)
+		return QCOM_SOC_PERF_SVS_L1;
+
+	if (load <= (u64)3840 * 2160 * 100)
+		return QCOM_SOC_PERF_NOMINAL;
+
+	return QCOM_SOC_PERF_TURBO;
+}
+
+static int op_jpeg_acquire(struct jenc_context *ectx, struct vb2_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+	struct qcom_jenc_queue *dq = &ectx->bufq[JENC_DST_QUEUE];
+	int rc;
+
+	/* Reset per-context stream state for each (re)acquire. */
+	sq->sequence = 0;
+	sq->buff_id = -1;
+	dq->sequence = 0;
+	dq->buff_id = -1;
+	/*
+	 * Recreate JPEG header lazily per destination buffer to tolerate
+	 * different valid V4L2 call orders (e.g. STREAMON before first QBUF).
+	 */
+	ectx->hdr_cache.size = 0;
+	/* Force DQT upload on first frame after (re)acquire. */
+	ectx->quality_programmed = 0;
+
+	if (atomic_inc_return(&jenc->ref_count) == 1) {
+		jenc->perf = jpeg_select_perf_level(ectx);
+		dev_dbg(jenc->dev, "%s: perf=%u src=%ux%u\n", __func__, jenc->perf,
+			sq->vf.width, sq->vf.height);
+
+		rc = pm_runtime_resume_and_get(jenc->dev);
+		if (rc < 0) {
+			dev_err(jenc->dev, "PM runtime get failed\n");
+			atomic_dec(&jenc->ref_count);
+			return rc;
+		}
+
+		rc = jpeg_init(jenc);
+		if (rc) {
+			dev_err(jenc->dev, "hardware init failed\n");
+			atomic_dec(&jenc->ref_count);
+			pm_runtime_put(jenc->dev);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int op_jpeg_release(struct jenc_context *ectx, struct vb2_queue *q)
+{
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	int rc = 0;
+	int pm_rc;
+	int ref;
+
+	ref = atomic_dec_if_positive(&jenc->ref_count);
+	if (ref < 0) {
+		WARN_ON_ONCE(1);
+		return 0;
+	}
+
+	if (!ref) {
+		rc = jpeg_deinit(jenc);
+		if (rc)
+			dev_err(jenc->dev, "hardware exit failed\n");
+
+		pm_rc = pm_runtime_put_sync(jenc->dev);
+		if (pm_rc < 0) {
+			dev_err(jenc->dev, "PM runtime put failed\n");
+			if (!rc)
+				rc = pm_rc;
+		}
+
+		dev_dbg(jenc->dev, "JPEG HW encoder released\n");
+	}
+
+	return rc;
+}
+
+const struct qcom_jpeg_hw_ops qcom_jpeg_default_ops = {
+	.hw_get_cap	= op_jpeg_get_hw_caps,
+	.hw_acquire	= op_jpeg_acquire,
+	.hw_release	= op_jpeg_release,
+	.hw_prepare	= op_jpeg_prepare,
+	.get_queue	= op_jpeg_get_buff_queue,
+	.queue_setup	= op_jpeg_queue_setup,
+	.src_fmt_update	= op_jpeg_src_fmt_update,
+	.buf_prepare	= op_jpeg_buffer_prepare,
+	.process_exec	= op_jpeg_process_exec,
+	.hw_irq_top	= op_jpeg_irq_top,
+	.hw_irq_bot	= op_jpeg_irq_bot
+};
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h
new file mode 100644
index 000000000000..857af4a24794
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_OPS_H
+#define QCOM_JENC_OPS_H
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <media/videobuf2-core.h>
+
+#include "qcom_jenc_dev.h"
+
+/*
+ * JENC encoder hardware operations.
+ */
+struct qcom_jpeg_hw_ops {
+	void (*hw_get_cap)
+		(struct qcom_jenc_dev *jenc_dev, u32 *hw_caps);
+
+	int (*hw_acquire)
+		(struct jenc_context *ectx, struct vb2_queue *queue);
+
+	int (*hw_release)
+		(struct jenc_context *ectx, struct vb2_queue *queue);
+
+	int (*hw_prepare)
+		(struct qcom_jenc_dev *jenc);
+
+	struct qcom_jenc_queue * (*get_queue)
+		(struct jenc_context *ectx, enum qcom_enc_qid id);
+
+	int (*queue_setup)
+		(struct jenc_context *ectx, enum qcom_enc_qid id);
+
+	int (*src_fmt_update)
+		(struct jenc_context *ectx, u32 old_fourcc, u32 new_fourcc);
+
+	int (*buf_prepare)
+		(struct jenc_context *ectx, struct vb2_buffer *vb2);
+
+	int (*process_exec)
+		(struct qcom_jenc_dev *jenc, struct jenc_context *ectx, struct vb2_buffer *vb2);
+
+	irqreturn_t (*hw_irq_top)(int irq_num, void *data);
+	irqreturn_t (*hw_irq_bot)(int irq_num, void *data);
+};
+
+extern const struct qcom_jpeg_hw_ops qcom_jpeg_default_ops;
+
+#endif /* QCOM_JENC_OPS_H */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_res.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.c
new file mode 100644
index 000000000000..bfe60187caa4
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk.h>
+
+#include "qcom_jenc_ops.h"
+#include "qcom_jenc_res.h"
+
+#include "qcom_jenc_v420_hw_info.h"
+
+#define QCOM_PERF_ROW(_axi_rate, _jpeg_rate) \
+{ \
+	.clk_rate = { \
+		[JPEG_CAMNOC_AXI_CLK]	= (_axi_rate),	\
+		[JPEG_CORE_CLK]		= (_jpeg_rate),	\
+	}, \
+}
+
+/*
+ * Baseline AXI clock rates shared across t165/t480, reused by later
+ * derivatives where the hardware does not change these domains.
+ */
+static const u64 cnoc_axi_clk_t165_t480[] = {
+	[QCOM_SOC_PERF_SUSPEND]	=  19200000,
+	[QCOM_SOC_PERF_LOWSVS]	= 300000000,
+	[QCOM_SOC_PERF_SVS]	= 300000000,
+	[QCOM_SOC_PERF_SVS_L1]	= 300000000,
+	[QCOM_SOC_PERF_NOMINAL]	= 400000000,
+	[QCOM_SOC_PERF_TURBO]	= 400000000,
+};
+
+/*
+ * Derivative with an improved CAMNOC AXI frequency range
+ */
+static const u64 cnoc_axi_clk_t680[] = {
+	[QCOM_SOC_PERF_SUSPEND]	=  19200000,
+	[QCOM_SOC_PERF_LOWSVS]	= 150000000,
+	[QCOM_SOC_PERF_SVS]	= 240000000,
+	[QCOM_SOC_PERF_SVS_L1]	= 320000000,
+	[QCOM_SOC_PERF_NOMINAL]	= 400000000,
+	[QCOM_SOC_PERF_TURBO]	= 480000000,
+};
+
+/*
+ * Baseline JPEG clock rates shared across t165/t480, reused by later
+ * derivatives where the hardware does not change these domains.
+ */
+static const u64 qcom_jpeg_clk_t165_t480[] = {
+	[QCOM_SOC_PERF_SUSPEND]	=  19200000,
+	[QCOM_SOC_PERF_LOWSVS]	= 300000000,
+	[QCOM_SOC_PERF_SVS]	= 400000000,
+	[QCOM_SOC_PERF_SVS_L1]	= 480000000,
+	[QCOM_SOC_PERF_NOMINAL]	= 600000000,
+	[QCOM_SOC_PERF_TURBO]	= 600000000,
+};
+
+/*
+ * Derivative with an improved maximum JPEG frequency
+ */
+static const u64 qcom_jpeg_clk_t780[] = {
+	[QCOM_SOC_PERF_SUSPEND]	=  19200000,
+	[QCOM_SOC_PERF_LOWSVS]	= 200000000,
+	[QCOM_SOC_PERF_SVS]	= 200000000,
+	[QCOM_SOC_PERF_SVS_L1]	= 400000000,
+	[QCOM_SOC_PERF_NOMINAL]	= 480000000,
+	[QCOM_SOC_PERF_TURBO]	= 785000000,
+};
+
+static const struct qcom_perf_resource qcom_perf_rates_t165_t480[] = {
+	[QCOM_SOC_PERF_SUSPEND]	=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SUSPEND],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SUSPEND]),
+
+	[QCOM_SOC_PERF_LOWSVS]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_LOWSVS],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_LOWSVS]),
+
+	[QCOM_SOC_PERF_SVS]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SVS],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SVS]),
+
+	[QCOM_SOC_PERF_SVS_L1]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SVS_L1],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SVS_L1]),
+
+	[QCOM_SOC_PERF_NOMINAL]	=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_NOMINAL],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_NOMINAL]),
+
+	[QCOM_SOC_PERF_TURBO]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_TURBO],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_TURBO]),
+};
+
+static const struct qcom_perf_resource qcom_perf_rates_v680[] = {
+	[QCOM_SOC_PERF_SUSPEND]	=
+		QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_SUSPEND],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SUSPEND]),
+
+	[QCOM_SOC_PERF_LOWSVS]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_LOWSVS],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_LOWSVS]),
+
+	[QCOM_SOC_PERF_SVS]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_SVS],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SVS]),
+
+	[QCOM_SOC_PERF_SVS_L1]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_SVS_L1],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_SVS_L1]),
+
+	[QCOM_SOC_PERF_NOMINAL]	=
+		QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_NOMINAL],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_NOMINAL]),
+
+	[QCOM_SOC_PERF_TURBO]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t680[QCOM_SOC_PERF_TURBO],
+			      qcom_jpeg_clk_t165_t480[QCOM_SOC_PERF_TURBO]),
+};
+
+static const struct qcom_perf_resource qcom_perf_rates_v780[] = {
+	[QCOM_SOC_PERF_SUSPEND]	=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SUSPEND],
+			      qcom_jpeg_clk_t780[QCOM_SOC_PERF_SUSPEND]),
+
+	[QCOM_SOC_PERF_LOWSVS]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_LOWSVS],
+			      qcom_jpeg_clk_t780[QCOM_SOC_PERF_LOWSVS]),
+
+	[QCOM_SOC_PERF_SVS]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SVS],
+			      qcom_jpeg_clk_t780[QCOM_SOC_PERF_SVS]),
+
+	[QCOM_SOC_PERF_SVS_L1]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_SVS_L1],
+			      qcom_jpeg_clk_t780[QCOM_SOC_PERF_SVS_L1]),
+
+	[QCOM_SOC_PERF_NOMINAL]	=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_NOMINAL],
+			      qcom_jpeg_clk_t780[QCOM_SOC_PERF_NOMINAL]),
+
+	[QCOM_SOC_PERF_TURBO]		=
+		QCOM_PERF_ROW(cnoc_axi_clk_t165_t480[QCOM_SOC_PERF_TURBO],
+			      qcom_jpeg_clk_t780[QCOM_SOC_PERF_TURBO]),
+};
+
+static const struct qcom_icc_resource qcom_jpeg_default_icc[] = {
+	{
+		.icc_id = "cam_ahb",
+		.pair	= { 38400, 76800 }
+	},
+	{
+		.icc_id = "cam_hf_0_mnoc",
+		.pair	= { 2097152, 2097152 }
+	},
+	{
+		.icc_id = "cam_sf_0_mnoc",
+		.pair	= { 0, 2097152 }
+	},
+	{
+		.icc_id	= "cam_sf_icp_mnoc",
+		.pair	= { 2097152, 2097152 }
+	},
+};
+
+/*
+ * Resources for T165, T170, T480 JPEG version and derivatives
+ */
+const struct qcom_dev_resources qcom_t165_t480_jpeg_drvdata = {
+	.hw_ops		= &qcom_jpeg_default_ops,
+	.hw_offs	= &qcom_jpeg_v420_hw_reg_offs,
+	.hw_mask	= &qcom_jpeg_v420_hw_reg_mask[0],
+	.icc_res	= qcom_jpeg_default_icc,
+	.num_of_icc	= ARRAY_SIZE(qcom_jpeg_default_icc),
+	.perf_cfg	= qcom_perf_rates_t165_t480,
+	.clk_names = {
+		[JPEG_CAMNOC_AXI_CLK]	= "camnoc_axi",
+		[JPEG_CORE_CLK]		= "jpeg",
+		[JPEG_CORE_AHB_CLK]	= "core_ahb",
+		[JPEG_CPAS_AHB_CLK]	= "cpas_ahb",
+		[JPEG_GCC_HF_AXI]	= "gcc_hf_axi",
+		[JPEG_GCC_SF_AXI]	= "gcc_sf_axi",
+	}
+};
+
+/*
+ * Resources for T680 JPEG version and derivatives
+ */
+const struct qcom_dev_resources qcom_t680_jpeg_drvdata = {
+	.hw_ops		= &qcom_jpeg_default_ops,
+	.hw_offs	= &qcom_jpeg_v420_hw_reg_offs,
+	.hw_mask	= &qcom_jpeg_v420_hw_reg_mask[0],
+	.icc_res	= qcom_jpeg_default_icc,
+	.num_of_icc	= ARRAY_SIZE(qcom_jpeg_default_icc),
+	.perf_cfg	= qcom_perf_rates_v680,
+	.clk_names = {
+		[JPEG_CAMNOC_AXI_CLK]	= "camnoc_axi",
+		[JPEG_CORE_CLK]		= "jpeg",
+		[JPEG_CORE_AHB_CLK]	= "core_ahb",
+		[JPEG_CPAS_AHB_CLK]	= "cpas_ahb",
+		[JPEG_GCC_HF_AXI]	= "gcc_hf_axi",
+		[JPEG_GCC_SF_AXI]	= "gcc_sf_axi",
+	}
+};
+
+/*
+ * Resources for T780 JPEG version and derivatives
+ */
+const struct qcom_dev_resources qcom_t780_jpeg_drvdata = {
+	.hw_ops		= &qcom_jpeg_default_ops,
+	.hw_offs	= &qcom_jpeg_v420_hw_reg_offs,
+	.hw_mask	= &qcom_jpeg_v420_hw_reg_mask[0],
+	.icc_res	= qcom_jpeg_default_icc,
+	.num_of_icc	= ARRAY_SIZE(qcom_jpeg_default_icc),
+	.perf_cfg	= qcom_perf_rates_v780,
+	.clk_names = {
+		[JPEG_CAMNOC_AXI_CLK]	= "camnoc_axi",
+		[JPEG_CORE_CLK]		= "jpeg",
+		[JPEG_CORE_AHB_CLK]	= "core_ahb",
+		[JPEG_CPAS_AHB_CLK]	= "cpas_ahb",
+		[JPEG_GCC_HF_AXI]	= "gcc_hf_axi",
+		[JPEG_GCC_SF_AXI]	= "gcc_sf_axi",
+	}
+};
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_res.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.h
new file mode 100644
index 000000000000..cdff41dbb10c
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_RES_H
+#define QCOM_JENC_RES_H
+
+#include "qcom_jenc_defs.h"
+
+/*
+ * clk_rate == 0 means: do not change this clock rate.
+ * Clock is still enabled/disabled normally.
+ */
+enum qcom_jpeg_clock_ids {
+	JPEG_CAMNOC_AXI_CLK,
+	JPEG_CORE_CLK,
+	JPEG_CORE_AHB_CLK,
+	JPEG_CPAS_AHB_CLK,
+	JPEG_GCC_HF_AXI,
+	JPEG_GCC_SF_AXI,
+	JPEG_MAX_CLOCKS
+};
+
+struct qcom_icc_resource {
+	const char *icc_id;
+	struct {
+		u32 aggr;
+		u32 peak;
+	} pair;
+};
+
+struct qcom_perf_resource {
+	u64 clk_rate[JPEG_MAX_CLOCKS];
+};
+
+struct qcom_dev_resources {
+	const struct qcom_jpeg_hw_ops	*hw_ops;
+	const struct qcom_jpeg_reg_offs	*hw_offs;
+	const u32			*hw_mask;
+
+	const struct qcom_icc_resource	*icc_res;
+	unsigned int			num_of_icc;
+	const struct qcom_perf_resource	*perf_cfg;
+	const char			*clk_names[JPEG_MAX_CLOCKS];
+};
+
+extern const struct qcom_dev_resources qcom_t165_t480_jpeg_drvdata;
+
+extern const struct qcom_dev_resources qcom_t680_jpeg_drvdata;
+
+extern const struct qcom_dev_resources qcom_t780_jpeg_drvdata;
+
+#endif	/* QCOM_JENC_RES_H */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
new file mode 100644
index 000000000000..3de747b0ec3d
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
@@ -0,0 +1,529 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_V420_HW_INFO_H
+#define QCOM_JENC_V420_HW_INFO_H
+
+#include "qcom_jenc_defs.h"
+
+#define JPEG_V420_HW_VER_STEP_POS 0
+#define JPEG_V420_HW_VER_STEP_MSK \
+	(0xffffu << JPEG_V420_HW_VER_STEP_POS)
+
+#define JPEG_V420_HW_VER_MINOR_POS 16
+#define JPEG_V420_HW_VER_MINOR_MSK \
+	(0x0fffu << JPEG_V420_HW_VER_MINOR_POS)
+
+#define JPEG_V420_HW_VER_MAJOR_POS 28
+#define JPEG_V420_HW_VER_MAJOR_MSK \
+	(0xfu << JPEG_V420_HW_VER_MAJOR_POS)
+
+#define JPEG_V420_HW_CAP_ENCODE_MSK BIT_U32(0)
+#define JPEG_V420_HW_CAP_DECODE_MSK BIT_U32(1)
+
+#define JPEG_V420_HW_CAP_UPSCALE_POS 4
+#define JPEG_V420_HW_CAP_UPSCALE_MSK \
+	(0x7u << JPEG_V420_HW_CAP_UPSCALE_POS)
+
+#define JPEG_V420_HW_CAP_DOWNSCALE_POS 8
+#define JPEG_V420_HW_CAP_DOWNSCALE_MSK \
+	(0x7u << JPEG_V420_HW_CAP_DOWNSCALE_POS)
+
+#define JPEG_V420_RST_CMD_FE_RESET_MSK			BIT_U32(0)
+#define JPEG_V420_RST_CMD_WE_RESET_MSK			BIT_U32(1)
+#define JPEG_V420_RST_CMD_ENCODER_RESET_MSK		BIT_U32(4)
+#define JPEG_V420_RST_CMD_DECODER_RESET_MSK		BIT_U32(5)
+#define JPEG_V420_RST_CMD_BLOCK_FORMATTER_RST_MSK	BIT_U32(6)
+#define JPEG_V420_RST_CMD_SCALE_RESET_MSK		BIT_U32(7)
+#define JPEG_V420_RST_CMD_REGISTER_RESET_MSK		BIT_U32(13)
+#define JPEG_V420_RST_CMD_MISR_RESET_MSK		BIT_U32(16)
+#define JPEG_V420_RST_CMD_CORE_RESET_MSK		BIT_U32(17)
+#define JPEG_V420_RST_CMD_JPEG_V420_DOMAIN_RESET_MSK	BIT_U32(29)
+#define JPEG_V420_RST_CMD_RESET_BYPASS_MSK		BIT_U32(31)
+
+#define JPEG_V420_CORE_CFG_FE_ENABLE_MSK	BIT_U32(0)
+#define JPEG_V420_CORE_CFG_WE_ENABLE_MSK	BIT_U32(1)
+#define JPEG_V420_CORE_CFG_ENC_ENABLE_MSK	BIT_U32(4)
+#define JPEG_V420_CORE_CFG_SCALE_ENABLE_MSK	BIT_U32(7)
+#define JPEG_V420_CORE_CFG_TESTBUS_ENABLE_MSK	BIT_U32(19)
+#define JPEG_V420_CORE_CFG_MODE_MSK		BIT_U32(24)
+#define JPEG_V420_CORE_CFG_CGC_DISABLE_MSK	BIT_U32(31)
+
+#define JPEG_V420_CMD_HW_START_MSK		BIT_U32(0)
+#define JPEG_V420_CMD_HW_STOP_MSK		BIT_U32(1)
+#define JPEG_V420_CMD_CLR_RD_PLN0_QUEUE_MSK	BIT_U32(4)
+#define JPEG_V420_CMD_CLR_RD_PLN1_QUEUE_MSK	BIT_U32(5)
+#define JPEG_V420_CMD_CLR_RD_PLN2_QUEUE_MSK	BIT_U32(6)
+#define JPEG_V420_CMD_CLR_WR_PLN0_QUEUE_MSK	BIT_U32(8)
+#define JPEG_V420_CMD_CLR_WR_PLN1_QUEUE_MSK	BIT_U32(9)
+#define JPEG_V420_CMD_CLR_WR_PLN2_QUEUE_MSK	BIT_U32(10)
+#define JPEG_V420_CMD_APPLY_SWC_RD_PARAMS_MSK	BIT_U32(11)
+
+#define JPEG_V420_CORE_STATE_STATUS_ENCODE_STATE_MSK	BIT_U32(0)
+#define JPEG_V420_CORE_STATE_STATUS_SCALE_STATE_MSK	BIT_U32(2)
+#define JPEG_V420_CORE_STATE_STATUS_REALTIME_STATE_MSK	BIT_U32(4)
+#define JPEG_V420_CORE_STATE_STATUS_BUS_STATE_MSK	BIT_U32(8)
+#define JPEG_V420_CORE_STATE_STATUS_CGC_STATE_MSK	BIT_U32(9)
+
+#define JPEG_V420_FE_CFG_BYTE_ORDERING_POS 0
+#define JPEG_V420_FE_CFG_BYTE_ORDERING_MSK \
+	(0xfu << JPEG_V420_FE_CFG_BYTE_ORDERING_POS)
+
+#define JPEG_V420_FE_CFG_BURST_LENGTH_MAX_POS 4
+#define JPEG_V420_FE_CFG_BURST_LENGTH_MAX_MSK \
+	(0xfu << JPEG_V420_FE_CFG_BURST_LENGTH_MAX_POS)
+
+#define JPEG_V420_FE_CFG_MEMORY_FORMAT_POS 8
+#define JPEG_V420_FE_CFG_MEMORY_FORMAT_MSK \
+	(0x3u << JPEG_V420_FE_CFG_MEMORY_FORMAT_POS)
+
+#define JPEG_V420_FE_CFG_CBCR_ORDER_MSK		BIT_U32(12)
+#define JPEG_V420_FE_CFG_BOTTOM_VPAD_EN_MSK	BIT_U32(13)
+#define JPEG_V420_FE_CFG_PLN0_EN_MSK		BIT_U32(16)
+#define JPEG_V420_FE_CFG_PLN1_EN_MSK		BIT_U32(17)
+#define JPEG_V420_FE_CFG_PLN2_EN_MSK		BIT_U32(18)
+#define JPEG_V420_FE_CFG_SIXTEEN_MCU_EN_MSK	BIT_U32(21)
+#define JPEG_V420_FE_CFG_MCUS_PER_BLOCK_MSK	BIT_U32(22)
+#define JPEG_V420_FE_CFG_MAL_BOUNDARY_MSK	BIT_U32(24)
+#define JPEG_V420_FE_CFG_MAL_EN_MSK		BIT_U32(27)
+
+#define JPEG_V420_PLN_RD_OFFS_OFFSET_POS 0
+#define JPEG_V420_PLN_RD_OFFS_OFFSET_MSK \
+	(0x1fffffffu << JPEG_V420_PLN_RD_OFFS_OFFSET_POS)
+
+#define JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_POS 0
+#define JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_MSK \
+	(0xffffu << JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_POS)
+
+#define JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_POS 16
+#define JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_MSK \
+	(0xffffu << JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_POS)
+
+#define JPEG_V420_PLN_RD_STRIDE_STRIDE_POS 0
+#define JPEG_V420_PLN_RD_STRIDE_STRIDE_MSK \
+	(0xffffu << JPEG_V420_PLN_RD_STRIDE_STRIDE_POS)
+
+#define JPEG_V420_PLN_RD_HINIT_FRACTIONAL_POS 0
+#define JPEG_V420_PLN_RD_HINIT_FRACTIONAL_MSK \
+	(0x1fffffu << JPEG_V420_PLN_RD_HINIT_FRACTIONAL_POS)
+
+#define JPEG_V420_PLN_RD_VINIT_FRACTIONAL_POS 0
+#define JPEG_V420_PLN_RD_VINIT_FRACTIONAL_MSK \
+	(0x1fffffu << JPEG_V420_PLN_RD_VINIT_FRACTIONAL_POS)
+
+#define JPEG_V420_WE_CFG_BYTE_ORDERING_POS 0
+#define JPEG_V420_WE_CFG_BYTE_ORDERING_MSK \
+	(0xfu << JPEG_V420_WE_CFG_BYTE_ORDERING_POS)
+
+#define JPEG_V420_WE_CFG_BURST_LENGTH_MAX_POS 4
+#define JPEG_V420_WE_CFG_BURST_LENGTH_MAX_MSK \
+	(0xfu << JPEG_V420_WE_CFG_BURST_LENGTH_MAX_POS)
+
+#define JPEG_V420_WE_CFG_MEMORY_FORMAT_POS 8
+#define JPEG_V420_WE_CFG_MEMORY_FORMAT_MSK \
+	(0x3u << JPEG_V420_WE_CFG_MEMORY_FORMAT_POS)
+
+#define JPEG_V420_WE_CFG_CBCR_ORDER_MSK		BIT_U32(12)
+#define JPEG_V420_WE_CFG_PLN0_EN_MSK		BIT_U32(16)
+#define JPEG_V420_WE_CFG_PLN1_EN_MSK		BIT_U32(17)
+#define JPEG_V420_WE_CFG_PLN2_EN_MSK		BIT_U32(18)
+#define JPEG_V420_WE_CFG_MAL_BOUNDARY_MSK	BIT_U32(24)
+#define JPEG_V420_WE_CFG_MAL_EN_MSK		BIT_U32(27)
+#define JPEG_V420_WE_CFG_POP_BUFF_ON_EOS_MSK	BIT_U32(28)
+
+#define JPEG_V420_PLN_WR_BUFF_SIZE_WIDTH_POS 0
+#define JPEG_V420_PLN_WR_BUFF_SIZE_WIDTH_MSK \
+	(0xffffu << JPEG_V420_PLN_WR_BUFF_SIZE_WIDTH_POS)
+
+#define JPEG_V420_PLN_WR_BUFF_SIZE_HEIGHT_POS 16
+#define JPEG_V420_PLN_WR_BUFF_SIZE_HEIGHT_MSK \
+	(0xffffu << JPEG_V420_PLN_WR_BUFF_SIZE_HEIGHT_POS)
+
+#define JPEG_V420_PLN_WR_STRIDE_STRIDE_POS 0
+#define JPEG_V420_PLN_WR_STRIDE_STRIDE_MSK \
+	(0xffffu << JPEG_V420_PLN_WR_STRIDE_STRIDE_POS)
+
+#define JPEG_V420_PLN_WR_HINIT_INTEGER_POS 0
+#define JPEG_V420_PLN_WR_HINIT_INTEGER_MSK \
+	(0xffffu << JPEG_V420_PLN_WR_HINIT_INTEGER_POS)
+
+#define JPEG_V420_PLN_WR_VINIT_INTEGER_POS 0
+#define JPEG_V420_PLN_WR_VINIT_INTEGER_MSK \
+	(0xffffu << JPEG_V420_PLN_WR_VINIT_INTEGER_POS)
+
+#define JPEG_V420_PLN_WR_HSTEP_INTEGER_POS 0
+#define JPEG_V420_PLN_WR_HSTEP_INTEGER_MSK \
+	(0x1ffffu << JPEG_V420_PLN_WR_HSTEP_INTEGER_POS)
+
+#define JPEG_V420_PLN_WR_VSTEP_INTEGER_POS 0
+#define JPEG_V420_PLN_WR_VSTEP_INTEGER_MSK \
+	(0x1ffffu << JPEG_V420_PLN_WR_VSTEP_INTEGER_POS)
+
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_POS 0
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_MSK \
+	(0xffffu << JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_POS)
+
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_POS 16
+#define JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_MSK \
+	(0xffffu << JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_POS)
+
+#define JPEG_V420_ENC_CFG_IMAGE_FORMAT_POS 0
+#define JPEG_V420_ENC_CFG_IMAGE_FORMAT_MSK \
+	(0x7u << JPEG_V420_ENC_CFG_IMAGE_FORMAT_POS)
+
+#define JPEG_V420_ENC_CFG_APPLY_EOI_MSK		BIT_U32(7)
+#define JPEG_V420_ENC_CFG_HUFFMAN_SEL_MSK	BIT_U32(8)
+#define JPEG_V420_ENC_CFG_FSC_ENABLE_MSK	BIT_U32(11)
+#define JPEG_V420_ENC_CFG_OUTPUT_DISABLE_MSK	BIT_U32(15)
+#define JPEG_V420_ENC_CFG_RST_MARKER_PERIOD_MSK	BIT_U32(16)
+
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_POS 0u
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_MSK \
+	(0x1fffu << JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_POS)
+
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_POS 16u
+#define JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_MSK \
+	(0x1fffu << JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_POS)
+
+#define JPEG_V420_OUTPUT_SIZE_STATUS_OUT_SIZE_BYTES_POS 0
+#define JPEG_V420_OUTPUT_SIZE_STATUS_OUT_SIZE_BYTES_MSK \
+	(0x1fffffff << JPEG_V420_OUTPUT_SIZE_STATUS_OUT_SIZE_BYTES_POS)
+
+#define JPEG_V420_SCALE_CFG_HSCALE_ENABLE_MSK		BIT_U32(4)
+#define JPEG_V420_SCALE_CFG_VSCALE_ENABLE_MSK		BIT_U32(5)
+#define JPEG_V420_SCALE_CFG_UPSAMPLE_EN_MSK		BIT_U32(6)
+#define JPEG_V420_SCALE_CFG_SUBSAMPLE_EN_MSK		BIT_U32(7)
+#define JPEG_V420_SCALE_CFG_HSCALE_ALGO_MSK		BIT_U32(8)
+#define JPEG_V420_SCALE_CFG_VSCALE_ALGO_MSK		BIT_U32(9)
+
+#define JPEG_V420_SCALE_CFG_H_SCALE_FIR_ALGO_POS  12u
+#define JPEG_V420_SCALE_CFG_H_SCALE_FIR_ALGO_MSK \
+	(0x3u << JPEG_V420_SCALE_CFG_H_SCALE_FIR_ALGO_POS)
+
+#define JPEG_V420_SCALE_CFG_V_SCALE_FIR_ALGO_POS  16u
+#define JPEG_V420_SCALE_CFG_V_SCALE_FIR_ALGO_MSK \
+	(0x3u << JPEG_V420_SCALE_CFG_V_SCALE_FIR_ALGO_POS)
+
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_POS 0
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_MSK \
+	(0xffu << JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_POS)
+
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_POS 16
+#define JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_MSK \
+	(0xfu << JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_POS)
+
+#define JPEG_V420_SCALE_PLN_HSTEP_FRACTIONAL_POS 0
+#define JPEG_V420_SCALE_PLN_HSTEP_FRACTIONAL_MSK \
+	(0x1fffffu << JPEG_V420_SCALE_PLN_HSTEP_FRACTIONAL_POS)
+
+#define JPEG_V420_SCALE_PLN_HSTEP_INTEGER_POS 21
+#define JPEG_V420_SCALE_PLN_HSTEP_INTEGER_MSK \
+	(0x3fu << JPEG_V420_SCALE_PLN_HSTEP_INTEGER_POS)
+
+#define JPEG_V420_SCALE_PLN_VSTEP_FRACTIONAL_POS 0
+#define JPEG_V420_SCALE_PLN_VSTEP_FRACTIONAL_MSK \
+	(0x1fffffu << JPEG_V420_SCALE_PLN_VSTEP_FRACTIONAL_POS)
+
+#define JPEG_V420_SCALE_PLN_VSTEP_INTEGER_POS 21
+#define JPEG_V420_SCALE_PLN_VSTEP_INTEGER_MSK \
+	(0x3fu << JPEG_V420_SCALE_PLN_VSTEP_INTEGER_POS)
+
+#define JPEG_V420_DMI_CFG_MEM_SEL_POS 0
+#define JPEG_V420_DMI_CFG_MEM_SEL_MSK \
+	(0x7u << JPEG_V420_DMI_CFG_MEM_SEL_POS)
+
+#define JPEG_V420_DMI_CFG_AUTO_INC_EN_MSK	BIT_U32(4)
+
+#define JPEG_V420_DMI_ADDR_ADDR_POS 0
+#define JPEG_V420_DMI_ADDR_ADDR_MSK \
+	(0x3ffu << JPEG_V420_DMI_ADDR_ADDR_POS)
+
+#define JPEG_V420_TESTBUS_CFG_BUS_SEL_POS 0
+#define JPEG_V420_TESTBUS_CFG_BUS_SEL_MSK \
+	(0x3fu << JPEG_V420_TESTBUS_CFG_BUS_SEL_POS)
+
+#define JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_POS 0
+#define JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_MSK \
+	(0x1fffu << JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_POS)
+
+#define JPEG_V420_PLN_RD_HINIT_INT_INTEGER_POS 0
+#define JPEG_V420_PLN_RD_HINIT_INT_INTEGER_MSK \
+	(0x1ffffu << JPEG_V420_PLN_RD_HINIT_INT_INTEGER_POS)
+
+#define JPEG_V420_PLN_RD_VINIT_INT_INTEGER_POS 0
+#define JPEG_V420_PLN_RD_VINIT_INT_INTEGER_MSK \
+	(0x1ffffu << JPEG_V420_PLN_RD_VINIT_INT_INTEGER_POS)
+
+#define JPEG_V420_IRQ_STATUS_SESSION_DONE_MSK		BIT_U32(0)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_DONE_MSK	BIT_U32(4)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_DONE_MSK	BIT_U32(5)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_DONE_MSK	BIT_U32(6)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_REQ_ATTN_MSK	BIT_U32(7)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_REQ_ATTN_MSK	BIT_U32(8)
+#define JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_REQ_ATTN_MSK	BIT_U32(9)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE_MSK	BIT_U32(10)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_DONE_MSK	BIT_U32(11)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_DONE_MSK	BIT_U32(12)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_REQ_ATTN_MSK	BIT_U32(13)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_REQ_ATTN_MSK	BIT_U32(14)
+#define JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_REQ_ATTN_MSK	BIT_U32(15)
+#define JPEG_V420_IRQ_STATUS_DCD_UNESCAPED_FF_MSK	BIT_U32(19)
+#define JPEG_V420_IRQ_STATUS_DCD_HUFFMAN_ERROR_MSK	BIT_U32(20)
+#define JPEG_V420_IRQ_STATUS_DCD_COEFF_ERROR_MSK	BIT_U32(21)
+#define JPEG_V420_IRQ_STATUS_DCD_MISSING_BITSTUFF_MSK	BIT_U32(22)
+#define JPEG_V420_IRQ_STATUS_DCD_SCAN_UNDERFLOW_MSK	BIT_U32(23)
+#define JPEG_V420_IRQ_STATUS_DCD_INVALID_RSM_MSK	BIT_U32(24)
+#define JPEG_V420_IRQ_STATUS_DCD_INVALID_RSM_SEQ_MSK	BIT_U32(25)
+#define JPEG_V420_IRQ_STATUS_DCD_MISSING_RSM_MSK	BIT_U32(26)
+#define JPEG_V420_IRQ_STATUS_STOP_ACK_MSK		BIT_U32(27)
+#define JPEG_V420_IRQ_STATUS_RESET_ACK_MSK		BIT_U32(28)
+
+#define	JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_DONE_MSK \
+	(JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_DONE_MSK	| \
+	 JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_DONE_MSK	| \
+	 JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_DONE_MSK)
+
+#define	JPEG_V420_IRQ_STATUS_WR_BUF_PLNS_DONE_MSK \
+	(JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE_MSK	| \
+	 JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_DONE_MSK	| \
+	 JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_DONE_MSK)
+
+#define	JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_REQ_ATTN_MSK \
+	(JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_REQ_ATTN_MSK	| \
+	 JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_REQ_ATTN_MSK	| \
+	 JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_REQ_ATTN_MSK)
+
+#define	JPEG_V420_IRQ_STATUS_WR_BUF_PLNS_REQ_ATTN_MSK \
+	(JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_REQ_ATTN_MSK	| \
+	 JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_REQ_ATTN_MSK	| \
+	 JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_REQ_ATTN_MSK)
+
+#define	JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK \
+	(JPEG_V420_IRQ_STATUS_DCD_UNESCAPED_FF_MSK	| \
+	 JPEG_V420_IRQ_STATUS_DCD_HUFFMAN_ERROR_MSK	| \
+	 JPEG_V420_IRQ_STATUS_DCD_COEFF_ERROR_MSK	| \
+	 JPEG_V420_IRQ_STATUS_DCD_MISSING_BITSTUFF_MSK	| \
+	 JPEG_V420_IRQ_STATUS_DCD_SCAN_UNDERFLOW_MSK	| \
+	 JPEG_V420_IRQ_STATUS_DCD_INVALID_RSM_MSK	| \
+	 JPEG_V420_IRQ_STATUS_DCD_INVALID_RSM_SEQ_MSK	| \
+	 JPEG_V420_IRQ_STATUS_DCD_MISSING_RSM_MSK)
+
+#define JPEG_V420_IRQ_STATUS_ALL_BITS \
+	(JPEG_V420_IRQ_STATUS_SESSION_DONE_MSK		| \
+	 JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_DONE_MSK	| \
+	 JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE_MSK	| \
+	 JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_REQ_ATTN_MSK	| \
+	 JPEG_V420_IRQ_STATUS_WR_BUF_PLNS_REQ_ATTN_MSK	| \
+	 JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK		| \
+	 JPEG_V420_IRQ_STATUS_STOP_ACK_MSK		| \
+	 JPEG_V420_IRQ_STATUS_RESET_ACK_MSK)
+
+const u32 qcom_jpeg_v420_hw_reg_mask[] = {
+	[JMSK_HW_VER_STEP]	= JPEG_V420_HW_VER_STEP_MSK,
+	[JMSK_HW_VER_MINOR]	= JPEG_V420_HW_VER_MINOR_MSK,
+	[JMSK_HW_VER_MAJOR]	= JPEG_V420_HW_VER_MAJOR_MSK,
+
+	[JMSK_HW_CAP_ENCODE]	= JPEG_V420_HW_CAP_ENCODE_MSK,
+	[JMSK_HW_CAP_DECODE]	= JPEG_V420_HW_CAP_DECODE_MSK,
+	[JMSK_HW_CAP_UPSCALE]	= JPEG_V420_HW_CAP_UPSCALE_MSK,
+	[JMSK_HW_CAP_DOWNSCALE]	= JPEG_V420_HW_CAP_DOWNSCALE_MSK,
+
+	[JMSK_RST_CMD_COMMON]	=
+		(JPEG_V420_RST_CMD_FE_RESET_MSK			|
+		 JPEG_V420_RST_CMD_WE_RESET_MSK			|
+		 JPEG_V420_RST_CMD_ENCODER_RESET_MSK		|
+		 JPEG_V420_RST_CMD_BLOCK_FORMATTER_RST_MSK	|
+		 JPEG_V420_RST_CMD_SCALE_RESET_MSK		|
+		 JPEG_V420_RST_CMD_REGISTER_RESET_MSK		|
+		 JPEG_V420_RST_CMD_MISR_RESET_MSK		|
+		 JPEG_V420_RST_CMD_CORE_RESET_MSK		|
+		 JPEG_V420_RST_CMD_JPEG_V420_DOMAIN_RESET_MSK),
+
+	[JMSK_RST_CMD_FE_RESET]			= JPEG_V420_RST_CMD_FE_RESET_MSK,
+	[JMSK_RST_CMD_WE_RESET]			= JPEG_V420_RST_CMD_WE_RESET_MSK,
+	[JMSK_RST_CMD_ENCODER_RESET]		= JPEG_V420_RST_CMD_ENCODER_RESET_MSK,
+	[JMSK_RST_CMD_DECODER_RESET]		= JPEG_V420_RST_CMD_DECODER_RESET_MSK,
+	[JMSK_RST_CMD_BLOCK_FORMATTER_RST]	= JPEG_V420_RST_CMD_BLOCK_FORMATTER_RST_MSK,
+	[JMSK_RST_CMD_SCALE_RESET]		= JPEG_V420_RST_CMD_SCALE_RESET_MSK,
+	[JMSK_RST_CMD_REGISTER_RESET]		= JPEG_V420_RST_CMD_REGISTER_RESET_MSK,
+	[JMSK_RST_CMD_MISR_RESET]		= JPEG_V420_RST_CMD_MISR_RESET_MSK,
+	[JMSK_RST_CMD_CORE_RESET]		= JPEG_V420_RST_CMD_CORE_RESET_MSK,
+	[JMSK_RST_CMD_JMSK_DOMAIN_RESET]	= JPEG_V420_RST_CMD_JPEG_V420_DOMAIN_RESET_MSK,
+	[JMSK_RST_CMD_RESET_BYPASS]		= JPEG_V420_RST_CMD_RESET_BYPASS_MSK,
+
+	[JMSK_CORE_CFG_FE_ENABLE]		= JPEG_V420_CORE_CFG_FE_ENABLE_MSK,
+	[JMSK_CORE_CFG_WE_ENABLE]		= JPEG_V420_CORE_CFG_WE_ENABLE_MSK,
+	[JMSK_CORE_CFG_ENC_ENABLE]		= JPEG_V420_CORE_CFG_ENC_ENABLE_MSK,
+	[JMSK_CORE_CFG_SCALE_ENABLE]		= JPEG_V420_CORE_CFG_SCALE_ENABLE_MSK,
+	[JMSK_CORE_CFG_TESTBUS_ENABLE]		= JPEG_V420_CORE_CFG_TESTBUS_ENABLE_MSK,
+	[JMSK_CORE_CFG_MODE]			= JPEG_V420_CORE_CFG_MODE_MSK,
+	[JMSK_CORE_CFG_CGC_DISABLE]		= JPEG_V420_CORE_CFG_CGC_DISABLE_MSK,
+
+	[JMSK_CMD_HW_START]			= JPEG_V420_CMD_HW_START_MSK,
+	[JMSK_CMD_HW_STOP]			= JPEG_V420_CMD_HW_STOP_MSK,
+
+	[JMSK_CMD_CLR_RD_PLNS_QUEUE]		=
+		(JPEG_V420_CMD_CLR_RD_PLN0_QUEUE_MSK |
+		 JPEG_V420_CMD_CLR_RD_PLN1_QUEUE_MSK |
+		 JPEG_V420_CMD_CLR_RD_PLN2_QUEUE_MSK),
+	[JMSK_CMD_CLR_WR_PLNS_QUEUE]		=
+		(JPEG_V420_CMD_CLR_WR_PLN0_QUEUE_MSK |
+		 JPEG_V420_CMD_CLR_WR_PLN1_QUEUE_MSK |
+		 JPEG_V420_CMD_CLR_WR_PLN2_QUEUE_MSK),
+
+	[JMSK_CMD_APPLY_SWC_RD_PARAMS]	= JPEG_V420_CMD_APPLY_SWC_RD_PARAMS_MSK,
+
+	[JMSK_CORE_STATUS_ENCODE_STATE]	= JPEG_V420_CORE_STATE_STATUS_ENCODE_STATE_MSK,
+	[JMSK_CORE_STATUS_SCALE_STATE]	= JPEG_V420_CORE_STATE_STATUS_SCALE_STATE_MSK,
+	[JMSK_CORE_STATUS_RT_STATE]	= JPEG_V420_CORE_STATE_STATUS_REALTIME_STATE_MSK,
+	[JMSK_CORE_STATUS_BUS_STATE]	= JPEG_V420_CORE_STATE_STATUS_BUS_STATE_MSK,
+	[JMSK_CORE_STATUS_CGC_STATE]	= JPEG_V420_CORE_STATE_STATUS_CGC_STATE_MSK,
+
+	[JMSK_IRQ_STATUS_SESSION_DONE]	= JPEG_V420_IRQ_STATUS_SESSION_DONE_MSK,
+
+	[JMSK_IRQ_STATUS_RD_BUF_PLN0_DONE]	= JPEG_V420_IRQ_STATUS_RD_BUF_PLN0_DONE_MSK,
+	[JMSK_IRQ_STATUS_RD_BUF_PLN1_DONE]	= JPEG_V420_IRQ_STATUS_RD_BUF_PLN1_DONE_MSK,
+	[JMSK_IRQ_STATUS_RD_BUF_PLN2_DONE]	= JPEG_V420_IRQ_STATUS_RD_BUF_PLN2_DONE_MSK,
+	[JMSK_IRQ_STATUS_RD_BUF_PLNS_ATTN]	= JPEG_V420_IRQ_STATUS_RD_BUF_PLNS_REQ_ATTN_MSK,
+
+	[JMSK_IRQ_STATUS_WR_BUF_PLN0_DONE]	= JPEG_V420_IRQ_STATUS_WR_BUF_PLN0_DONE_MSK,
+	[JMSK_IRQ_STATUS_WR_BUF_PLN1_DONE]	= JPEG_V420_IRQ_STATUS_WR_BUF_PLN1_DONE_MSK,
+	[JMSK_IRQ_STATUS_WR_BUF_PLN2_DONE]	= JPEG_V420_IRQ_STATUS_WR_BUF_PLN2_DONE_MSK,
+	[JMSK_IRQ_STATUS_WR_BUF_PLNS_ATTN]	= JPEG_V420_IRQ_STATUS_WR_BUF_PLNS_REQ_ATTN_MSK,
+
+	[JMSK_IRQ_STATUS_SESSION_ERROR]	= JPEG_V420_IRQ_STATUS_SESION_ERROR_MSK,
+
+	[JMSK_IRQ_STATUS_STOP_ACK]	= JPEG_V420_IRQ_STATUS_STOP_ACK_MSK,
+	[JMSK_IRQ_STATUS_RESET_ACK]	= JPEG_V420_IRQ_STATUS_RESET_ACK_MSK,
+
+	[JMSK_IRQ_STATUS_ALL_BITS]	= JPEG_V420_IRQ_STATUS_ALL_BITS,
+
+	[JMSK_FE_CFG_BYTE_ORDERING]	= JPEG_V420_FE_CFG_BYTE_ORDERING_MSK,
+	[JMSK_FE_CFG_BURST_LENGTH_MAX]	= JPEG_V420_FE_CFG_BURST_LENGTH_MAX_MSK,
+	[JMSK_FE_CFG_MEMORY_FORMAT]	= JPEG_V420_FE_CFG_MEMORY_FORMAT_MSK,
+	[JMSK_FE_CFG_CBCR_ORDER]	= JPEG_V420_FE_CFG_CBCR_ORDER_MSK,
+	[JMSK_FE_CFG_BOTTOM_VPAD_EN]	= JPEG_V420_FE_CFG_BOTTOM_VPAD_EN_MSK,
+	[JMSK_FE_CFG_PLN0_EN]		= JPEG_V420_FE_CFG_PLN0_EN_MSK,
+	[JMSK_FE_CFG_PLN1_EN]		= JPEG_V420_FE_CFG_PLN1_EN_MSK,
+	[JMSK_FE_CFG_PLN2_EN]		= JPEG_V420_FE_CFG_PLN2_EN_MSK,
+	[JMSK_FE_CFG_SIXTEEN_MCU_EN]	= JPEG_V420_FE_CFG_SIXTEEN_MCU_EN_MSK,
+	[JMSK_FE_CFG_MCUS_PER_BLOCK]	= JPEG_V420_FE_CFG_MCUS_PER_BLOCK_MSK,
+	[JMSK_FE_CFG_MAL_BOUNDARY]	= JPEG_V420_FE_CFG_MAL_BOUNDARY_MSK,
+	[JMSK_FE_CFG_MAL_EN]		= JPEG_V420_FE_CFG_MAL_EN_MSK,
+
+	[JMSK_FE_VBPAD_CFG_BLOCK_ROW]	= JPEG_V420_FE_VBPAD_CFG_BLOCK_ROW_MSK,
+
+	[JMSK_PLNS_RD_OFFSET]		= JPEG_V420_PLN_RD_OFFS_OFFSET_MSK,
+	[JMSK_PLNS_RD_BUF_SIZE_WIDTH]	= JPEG_V420_PLN_RD_BUFF_SIZE_WIDTH_MSK,
+	[JMSK_PLNS_RD_BUF_SIZE_HEIGHT]	= JPEG_V420_PLN_RD_BUFF_SIZE_HEIGHT_MSK,
+	[JMSK_PLNS_RD_STRIDE]		= JPEG_V420_PLN_RD_STRIDE_STRIDE_MSK,
+	[JMSK_PLNS_RD_HINIT]		= JPEG_V420_PLN_RD_HINIT_FRACTIONAL_MSK,
+	[JMSK_PLNS_RD_VINIT]		= JPEG_V420_PLN_RD_VINIT_FRACTIONAL_MSK,
+
+	[JMSK_WE_CFG_BYTE_ORDERING]	= JPEG_V420_WE_CFG_BYTE_ORDERING_MSK,
+	[JMSK_WE_CFG_BURST_LENGTH_MAX]	= JPEG_V420_WE_CFG_BURST_LENGTH_MAX_MSK,
+	[JMSK_WE_CFG_MEMORY_FORMAT]	= JPEG_V420_WE_CFG_MEMORY_FORMAT_MSK,
+	[JMSK_WE_CFG_CBCR_ORDER]	= JPEG_V420_WE_CFG_CBCR_ORDER_MSK,
+	[JMSK_WE_CFG_PLN0_EN]		= JPEG_V420_WE_CFG_PLN0_EN_MSK,
+	[JMSK_WE_CFG_PLN1_EN]		= JPEG_V420_WE_CFG_PLN1_EN_MSK,
+	[JMSK_WE_CFG_PLN2_EN]		= JPEG_V420_WE_CFG_PLN2_EN_MSK,
+	[JMSK_WE_CFG_MAL_BOUNDARY]	= JPEG_V420_WE_CFG_MAL_BOUNDARY_MSK,
+	[JMSK_WE_CFG_MAL_EN]		= JPEG_V420_WE_CFG_MAL_EN_MSK,
+	[JMSK_WE_CFG_POP_BUFF_ON_EOS]	= JPEG_V420_WE_CFG_POP_BUFF_ON_EOS_MSK,
+
+	[JMSK_PLNS_WR_BUF_SIZE_WIDTH]	= JPEG_V420_PLN_WR_BUFF_SIZE_WIDTH_MSK,
+	[JMSK_PLNS_WR_BUF_SIZE_HEIGHT]	= JPEG_V420_PLN_WR_BUFF_SIZE_HEIGHT_MSK,
+
+	[JMSK_PLNS_WR_STRIDE]	= JPEG_V420_PLN_WR_STRIDE_STRIDE_MSK,
+	[JMSK_PLNS_WR_HINIT]	= JPEG_V420_PLN_WR_HINIT_INTEGER_MSK,
+	[JMSK_PLNS_WR_VINIT]	= JPEG_V420_PLN_WR_VINIT_INTEGER_MSK,
+	[JMSK_PLNS_WR_HSTEP]	= JPEG_V420_PLN_WR_HSTEP_INTEGER_MSK,
+	[JMSK_PLNS_WR_VSTEP]	= JPEG_V420_PLN_WR_VSTEP_INTEGER_MSK,
+
+	[JMSK_PLNS_WR_BLOCK_CFG_PER_COL]	= JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_COL_MSK,
+	[JMSK_PLNS_WR_BLOCK_CFG_PER_RAW]	= JPEG_V420_PLN_WR_BLK_CFG_BLOCKS_PER_ROW_MSK,
+
+	[JMSK_SCALE_CFG_HSCALE_ENABLE]		= JPEG_V420_SCALE_CFG_HSCALE_ENABLE_MSK,
+	[JMSK_SCALE_CFG_VSCALE_ENABLE]		= JPEG_V420_SCALE_CFG_VSCALE_ENABLE_MSK,
+	[JMSK_SCALE_CFG_UPSAMPLE_EN]		= JPEG_V420_SCALE_CFG_UPSAMPLE_EN_MSK,
+	[JMSK_SCALE_CFG_SUBSAMPLE_EN]		= JPEG_V420_SCALE_CFG_SUBSAMPLE_EN_MSK,
+	[JMSK_SCALE_CFG_HSCALE_ALGO]		= JPEG_V420_SCALE_CFG_HSCALE_ALGO_MSK,
+	[JMSK_SCALE_CFG_VSCALE_ALGO]		= JPEG_V420_SCALE_CFG_VSCALE_ALGO_MSK,
+	[JMSK_SCALE_CFG_H_SCALE_FIR_ALGO]	= JPEG_V420_SCALE_CFG_H_SCALE_FIR_ALGO_MSK,
+	[JMSK_SCALE_CFG_V_SCALE_FIR_ALGO]	= JPEG_V420_SCALE_CFG_V_SCALE_FIR_ALGO_MSK,
+
+	[JMSK_SCALE_PLNS_OUT_CFG_BLK_WIDTH]	= JPEG_V420_SCALE_OUT_CFG_BLOCK_WIDTH_MSK,
+	[JMSK_SCALE_PLNS_OUT_CFG_BLK_HEIGHT]	= JPEG_V420_SCALE_OUT_CFG_BLOCK_HEIGHT_MSK,
+
+	[JMSK_SCALE_PLNS_HSTEP_FRACTIONAL]	= JPEG_V420_SCALE_PLN_HSTEP_FRACTIONAL_MSK,
+	[JMSK_SCALE_PLNS_HSTEP_INTEGER]		= JPEG_V420_SCALE_PLN_HSTEP_INTEGER_MSK,
+	[JMSK_SCALE_PLNS_VSTEP_FRACTIONAL]	= JPEG_V420_SCALE_PLN_VSTEP_FRACTIONAL_MSK,
+	[JMSK_SCALE_PLNS_VSTEP_INTEGER]		= JPEG_V420_SCALE_PLN_VSTEP_INTEGER_MSK,
+
+	[JMSK_ENC_CFG_IMAGE_FORMAT]		= JPEG_V420_ENC_CFG_IMAGE_FORMAT_MSK,
+	[JMSK_ENC_CFG_APPLY_EOI]		= JPEG_V420_ENC_CFG_APPLY_EOI_MSK,
+	[JMSK_ENC_CFG_HUFFMAN_SEL]		= JPEG_V420_ENC_CFG_HUFFMAN_SEL_MSK,
+	[JMSK_ENC_CFG_FSC_ENABLE]		= JPEG_V420_ENC_CFG_FSC_ENABLE_MSK,
+	[JMSK_ENC_CFG_OUTPUT_DISABLE]		= JPEG_V420_ENC_CFG_OUTPUT_DISABLE_MSK,
+	[JMSK_ENC_CFG_RST_MARKER_PERIOD]	= JPEG_V420_ENC_CFG_RST_MARKER_PERIOD_MSK,
+	[JMSK_ENC_IMAGE_SIZE_WIDTH]		= JPEG_V420_ENC_IMG_SIZE_ENCODE_WIDTH_MSK,
+	[JMSK_ENC_IMAGE_SIZE_HEIGHT]		= JPEG_V420_ENC_IMG_SIZE_ENCODE_HEIGHT_MSK,
+};
+
+const struct qcom_jpeg_reg_offs qcom_jpeg_v420_hw_reg_offs = {
+	.hw_version	= 0x000,
+	.hw_capability	= 0x004,
+	.reset_cmd	= 0x008,
+	.core_cfg	= 0x00c,
+	.hw_cmd		= 0x010,
+	.int_mask	= 0x018,
+	.int_clr	= 0x01c,
+	.int_status	= 0x020,
+	.enc_core_state = 0x014,
+
+	.fe = {
+		.pntr	= { 0x038, 0x044, 0x050 },
+		.offs	= { 0x03c, 0x048, 0x054 },
+		.cnsmd	= { 0x040, 0x04c, 0x058 },
+		.bsize	= { 0x060, 0x068, 0x070 },
+		.stride	= { 0x064, 0x06c, 0x08c },
+		.hinit	= { 0x074, 0x078, 0x07c },
+		.vinit	= { 0x080, 0x084, 0x088 },
+		.pntr_cnt	= 0x05c,
+		.vbpad_cfg	= 0x2e8
+	},
+	.fe_cfg	= 0x024,
+
+	.we = {
+		.pntr	= { 0x0cc, 0x0d0, 0x0d4 },
+		.cnsmd	= { 0x0d8, 0x0dc, 0x0e0 },
+		.bsize	= { 0x0e8, 0x0ec, 0x0f0 },
+		.stride	= { 0x0f4, 0x0f8, 0x0fc },
+		.hinit	= { 0x100, 0x104, 0x108 },
+		.hstep	= { 0x118, 0x11c, 0x120 },
+		.vinit	= { 0x10c, 0x110, 0x114 },
+		.vstep	= { 0x124, 0x128, 0x12c },
+		.blocks	= { 0x130, 0x134, 0x138 },
+		.pntr_cnt = 0x0e4
+	},
+	.we_cfg	= 0x0c0,
+
+	.scale = {
+		.hstep	= { 0x27c, 0x280, 0x284 },
+		.vstep	= { 0x28c, 0x290, 0x294 },
+	},
+	.scale_cfg	= 0x26c,
+	.scale_out_cfg	= { 0x270, 0x274, 0x278 },
+
+	.enc_cfg	= 0x13c,
+	.enc_img_size	= 0x140,
+	.enc_out_size	= 0x180,
+
+	.dmi_cfg	= 0x298,
+	.dmi_data	= 0x2a0,
+	.dmi_addr	= 0x29c,
+};
+
+#endif /* QCOM_JENC_V420_HW_INFO_H */
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
new file mode 100644
index 000000000000..cfaa2aede965
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
@@ -0,0 +1,1109 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-dma-sg.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "qcom_jenc_dev.h"
+#include "qcom_jenc_v4l2.h"
+#include "qcom_jenc_ops.h"
+#include "qcom_jenc_defs.h"
+
+static const struct v4l2_frmsizeenum jpeg_def_frmsize = {
+	.stepwise = {
+		.min_width	= QCOM_JPEG_HW_MIN_WIDTH,
+		.max_width	= QCOM_JPEG_HW_MAX_WIDTH,
+		.step_width	= QCOM_JPEG_HW_DEF_HSTEP,
+		.min_height	= QCOM_JPEG_HW_MIN_HEIGHT,
+		.max_height	= QCOM_JPEG_HW_MAX_HEIGHT,
+		.step_height	= QCOM_JPEG_HW_DEF_VSTEP,
+	},
+	.type = V4L2_FRMSIZE_TYPE_STEPWISE
+};
+
+static const struct jenc_enc_format jpeg_src_formats[] = {
+	{
+		.fourcc	= V4L2_PIX_FMT_NV12M,
+		.type	= V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+	},
+	{
+		.fourcc	= V4L2_PIX_FMT_NV21M,
+		.type	= V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+	},
+	{
+		.fourcc	= V4L2_PIX_FMT_GREY,
+		.type	= V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+	},
+};
+
+#define JPEG_SRC_FMT_COUNT ARRAY_SIZE(jpeg_src_formats)
+
+static const struct jenc_enc_format jpeg_dst_formats[] = {
+	{
+		.fourcc	= V4L2_PIX_FMT_JPEG,
+		.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+	}
+};
+
+#define JPEG_DST_FMT_COUNT ARRAY_SIZE(jpeg_dst_formats)
+
+static inline struct jenc_context *jpeg_file2ctx(struct file *file)
+{
+	struct v4l2_fh *fh = (file_to_v4l2_fh(file));
+
+	if (!fh)
+		return NULL;
+
+	return container_of(fh, struct jenc_context, fh);
+}
+
+static struct qcom_jenc_queue *jpeg_get_bufq(struct jenc_context *ectx, enum qcom_enc_qid id)
+{
+	return &ectx->bufq[id];
+}
+
+static bool jpeg_v4l2_queues_busy(struct jenc_context *ctx)
+{
+	struct vb2_queue *out_q;
+	struct vb2_queue *cap_q;
+
+	out_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+
+	cap_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
+	return vb2_is_busy(out_q) || vb2_is_busy(cap_q);
+}
+
+static bool jpeg_is_invalid_src(struct jenc_context *ectx, u32 type)
+{
+	bool is_invalid = (type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+
+	if (is_invalid)
+		dev_err(ectx->dev, "invalid src type or format\n");
+
+	return is_invalid;
+}
+
+static bool jpeg_is_invalid_dst(struct jenc_context *ectx, u32 type)
+{
+	bool is_invalid = (type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
+	if (is_invalid)
+		dev_err(ectx->dev, "invalid dst type or format\n");
+
+	return is_invalid;
+}
+
+static const struct jenc_enc_format *jpeg_find_pix_format(enum qcom_enc_qid id, u32 fourcc)
+{
+	const struct jenc_enc_format *efmt;
+	unsigned int i, count;
+
+	if (id == JENC_SRC_QUEUE) {
+		count = JPEG_SRC_FMT_COUNT;
+		efmt = &jpeg_src_formats[0];
+	} else {
+		count = JPEG_DST_FMT_COUNT;
+		efmt = &jpeg_dst_formats[0];
+	}
+
+	for (i = 0; i < count; i++) {
+		if (efmt[i].fourcc == fourcc)
+			return &efmt[i];
+	}
+
+	return NULL;
+}
+
+static const struct jenc_enc_format *jpeg_get_format(struct jenc_context *ectx,
+						     enum qcom_enc_qid qid, u32 pixelformat)
+{
+	const struct jenc_enc_format *efmt = jpeg_find_pix_format(qid, pixelformat);
+
+	if (!efmt)
+		efmt = (qid == JENC_SRC_QUEUE) ? &jpeg_src_formats[0] : &jpeg_dst_formats[0];
+
+	return efmt;
+}
+
+static int jpeg_update_src_planes(const struct jenc_enc_format *ef, struct v4l2_format *v4f)
+{
+	struct v4l2_pix_format_mplane *f = &v4f->fmt.pix_mp;
+	const struct v4l2_format_info *info = v4l2_format_info(ef->fourcc);
+	int rc;
+
+	if (!info)
+		return -EINVAL;
+
+	f->pixelformat = ef->fourcc;
+
+	f->field	= V4L2_FIELD_NONE;
+	f->colorspace	= V4L2_COLORSPACE_SRGB;
+	f->xfer_func	= V4L2_MAP_XFER_FUNC_DEFAULT(f->colorspace);
+	f->ycbcr_enc	= V4L2_MAP_YCBCR_ENC_DEFAULT(f->colorspace);
+	f->quantization =
+		V4L2_MAP_QUANTIZATION_DEFAULT(f->ycbcr_enc == V4L2_YCBCR_ENC_601,
+					      f->colorspace, f->ycbcr_enc);
+
+	v4l2_apply_frmsize_constraints(&f->width, &f->height, &jpeg_def_frmsize.stepwise);
+
+	rc = v4l2_fill_pixfmt_mp(f, ef->fourcc, f->width, f->height);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static void jpeg_update_dst_plane(const struct jenc_enc_format *ef, struct v4l2_format *v4f)
+{
+	struct v4l2_pix_format_mplane *f = &v4f->fmt.pix_mp;
+	u64 size;
+	u32 nbx, nby;
+
+	f->pixelformat  = ef->fourcc;
+	f->field	= V4L2_FIELD_NONE;
+	f->colorspace	= V4L2_COLORSPACE_SRGB;
+	f->xfer_func	= V4L2_MAP_XFER_FUNC_DEFAULT(f->colorspace);
+	f->ycbcr_enc	= V4L2_MAP_YCBCR_ENC_DEFAULT(f->colorspace);
+	f->quantization =
+		V4L2_MAP_QUANTIZATION_DEFAULT(f->ycbcr_enc == V4L2_YCBCR_ENC_601,
+					      f->colorspace, f->ycbcr_enc);
+
+	v4l2_apply_frmsize_constraints(&f->width, &f->height, &jpeg_def_frmsize.stepwise);
+
+	/*
+	 * JPEG is a variable-size format. The output size cannot be derived
+	 * from bits per point or line stride.
+	 *
+	 * Provide a conservative upper bound based on worst-case entropy
+	 * coding of 8x8 DCT blocks:
+	 *
+	 * - Each 8x8 block has 64 coefficients (1 DC + 63 AC).
+	 * - In worst-case (high-entropy input, low quantization), all
+	 *   coefficients may be non-zero.
+	 * - Huffman coding then emits (code + magnitude bits) per coefficient,
+	 *   which can approach ~2 bytes per coefficient in the worst case.
+	 *
+	 * => Worst-case is 64 coefficients * 2 bytes = 128 bytes per 8x8 block
+	 * => approximately 2 bytes per point
+	 *
+	 * This bound implicitly covers byte stuffing (0xFF escaping) and is
+	 * conservative with respect to subsampled formats (e.g. 4:2:0).
+	 *
+	 * Additional margin is added for headers and alignment.
+	 *
+	 * Note: This is a conservative upper bound, not an exact size.
+	 */
+
+	nbx = DIV_ROUND_UP(f->width,  8);
+	nby = DIV_ROUND_UP(f->height, 8);
+
+	size = nbx * nby * 128;
+	size += SZ_4K; /* header + safety margin */
+
+	f->plane_fmt[0].bytesperline = 0;
+	f->plane_fmt[0].sizeimage    = ALIGN(size, SZ_4K);
+}
+
+static int jpeg_enum_fmt_src(struct v4l2_fmtdesc *f)
+{
+	if (f->index >= JPEG_SRC_FMT_COUNT)
+		return -EINVAL;
+
+	f->pixelformat = jpeg_src_formats[f->index].fourcc;
+
+	return 0;
+}
+
+static int jpeg_enum_fmt_dst(struct v4l2_fmtdesc *f)
+{
+	if (f->index >= JPEG_DST_FMT_COUNT)
+		return -EINVAL;
+
+	f->pixelformat = jpeg_dst_formats[f->index].fourcc;
+
+	return 0;
+}
+
+static int jpeg_v4l2_try_format(struct jenc_context *ectx, struct v4l2_format *f)
+{
+	struct v4l2_pix_format_mplane *pm = &f->fmt.pix_mp;
+	const struct jenc_enc_format *ef;
+	int rc;
+
+	/* The function always returns valid driver format */
+	ef = jpeg_get_format(ectx, TYPE2QID(f->type), pm->pixelformat);
+
+	dev_dbg(ectx->dev, "type=%d %c%c%c%c\n",
+		TYPE2QID(f->type),
+		(ef->fourcc >>  0) & 0xff,
+		(ef->fourcc >>  8) & 0xff,
+		(ef->fourcc >> 16) & 0xff,
+		(ef->fourcc >> 24) & 0xff);
+
+	if (V4L2_TYPE_IS_CAPTURE(f->type)) {
+		f->fmt.pix_mp.num_planes = 1;
+
+		jpeg_update_dst_plane(ef, f);
+
+		dev_dbg(ectx->dev, "\tImage: %dx%d Size:%9d\n", f->fmt.pix_mp.width,
+			f->fmt.pix_mp.height, f->fmt.pix_mp.plane_fmt[0].sizeimage);
+	} else {
+		/*
+		 * The used format is an internal driver format that must be
+		 * present in the V4L2 common formats; therefore, the errors
+		 * below should never occur.
+		 */
+		const struct v4l2_format_info *info = v4l2_format_info(ef->fourcc);
+		u8 pln = 0;
+
+		if (!info)
+			return -EINVAL;
+
+		f->fmt.pix_mp.num_planes = info->comp_planes;
+
+		rc = jpeg_update_src_planes(ef, f);
+		if (rc)
+			return rc;
+
+		for (pln = 0; pln < f->fmt.pix_mp.num_planes; pln++)
+			dev_dbg(ectx->dev, "\tImage: %dx%d BPL:%5d Size:%9d\n",
+				pm->width, pm->height, pm->plane_fmt[pln].bytesperline,
+				pm->plane_fmt[pln].sizeimage);
+	}
+
+	return 0;
+}
+
+static int jpeg_v4l2_set_defaults(struct jenc_context *ectx)
+{
+	struct qcom_jenc_queue *sq = jpeg_get_bufq(ectx, JENC_SRC_QUEUE);
+	struct qcom_jenc_queue *dq = jpeg_get_bufq(ectx, JENC_DST_QUEUE);
+	struct v4l2_format f = {0};
+	int rc;
+
+	f.type			 = jpeg_src_formats->type;
+	f.fmt.pix_mp.pixelformat = jpeg_src_formats->fourcc;
+	f.fmt.pix_mp.width	 = QCOM_JPEG_HW_DEF_WIDTH;
+	f.fmt.pix_mp.height	 = QCOM_JPEG_HW_DEF_HEIGHT;
+
+	rc = jpeg_v4l2_try_format(ectx, &f);
+	if (rc)
+		return rc;
+
+	sq->vf = f.fmt.pix_mp;
+
+	f.type			 = jpeg_dst_formats->type;
+	f.fmt.pix_mp.pixelformat = jpeg_dst_formats->fourcc;
+	f.fmt.pix_mp.width	 = QCOM_JPEG_HW_DEF_WIDTH;
+	f.fmt.pix_mp.height	 = QCOM_JPEG_HW_DEF_HEIGHT;
+
+	rc = jpeg_v4l2_try_format(ectx, &f);
+	if (rc)
+		return rc;
+
+	dq->vf = f.fmt.pix_mp;
+
+	return 0;
+}
+
+static int jpeg_v4l2_set_format(struct jenc_context *ectx, struct v4l2_format *f)
+{
+	const struct qcom_jpeg_hw_ops *hw = ectx->jenc->res->hw_ops;
+	struct qcom_jenc_queue *q = jpeg_get_bufq(ectx, TYPE2QID(f->type));
+	struct qcom_jenc_queue *sq = jpeg_get_bufq(ectx, JENC_SRC_QUEUE);
+	struct v4l2_pix_format_mplane *pm = &f->fmt.pix_mp;
+	u32 old_src_fourcc = sq->vf.pixelformat;
+	int rc;
+
+	if (jpeg_v4l2_queues_busy(ectx))
+		return -EBUSY;
+
+	if (!v4l2_m2m_get_vq(ectx->fh.m2m_ctx, f->type)) {
+		dev_err(ectx->dev, "cannot get video queue\n");
+		return -EINVAL;
+	}
+
+	rc = jpeg_v4l2_try_format(ectx, f);
+	if (rc)
+		return rc;
+
+	/*
+	 * Because scaling is not supported, source and destination image
+	 * sizes must be equal.
+	 */
+	if (V4L2_TYPE_IS_CAPTURE(f->type)) {
+		/* Adjust source size to match capture size */
+		if (pm->width != sq->vf.width || pm->height != sq->vf.height) {
+			struct v4l2_format nf = {0};
+
+			nf.type			  = jpeg_src_formats->type;
+			nf.fmt.pix_mp.pixelformat = sq->vf.pixelformat;
+			nf.fmt.pix_mp.width	  = pm->width;
+			nf.fmt.pix_mp.height	  = pm->height;
+
+			rc = jpeg_v4l2_try_format(ectx, &nf);
+			if (rc)
+				return rc;
+
+			sq->vf = nf.fmt.pix_mp;
+		}
+
+	} else {
+		struct qcom_jenc_queue *dq = jpeg_get_bufq(ectx, JENC_DST_QUEUE);
+		struct v4l2_format nf = {0};
+
+		/* Adjust destination size to match source size */
+		if (pm->width != dq->vf.width || pm->height != dq->vf.height) {
+			nf.type			  = jpeg_dst_formats->type;
+			nf.fmt.pix_mp.pixelformat = dq->vf.pixelformat;
+			nf.fmt.pix_mp.width	  = pm->width;
+			nf.fmt.pix_mp.height	  = pm->height;
+
+			rc = jpeg_v4l2_try_format(ectx, &nf);
+			if (rc)
+				return rc;
+
+			dq->vf = nf.fmt.pix_mp;
+
+			/*
+			 * The horizontal alignment of the destination is larger, and the
+			 * result after adjustment may still differ. In this case, the
+			 * requested image size should also be modified.
+			 */
+			if (pm->width != nf.fmt.pix_mp.width ||
+			    pm->height != nf.fmt.pix_mp.height) {
+				pm->width  = nf.fmt.pix_mp.width;
+				pm->height = nf.fmt.pix_mp.height;
+			}
+		}
+	}
+
+	q->vf = *pm;
+
+	if (V4L2_TYPE_IS_OUTPUT(f->type) && hw->src_fmt_update) {
+		rc = hw->src_fmt_update(ectx, old_src_fourcc, q->vf.pixelformat);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
+static void jpeg_v4l2_get_format(struct jenc_context *ectx, struct v4l2_format *f)
+{
+	struct qcom_jenc_queue *q = jpeg_get_bufq(ectx, TYPE2QID(f->type));
+
+	f->fmt.pix_mp = q->vf;
+}
+
+static void jpeg_v4l2_work_stop(struct jenc_context *ctx, enum vb2_buffer_state buff_state);
+
+static void jpeg_finish_work(struct work_struct *work)
+{
+	struct jenc_context *ctx = container_of(work, struct jenc_context, finish_work);
+
+	v4l2_m2m_job_finish(ctx->jenc->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+static void jpeg_stop_work(struct work_struct *work)
+{
+	struct jenc_context *ctx = container_of(work, struct jenc_context, stop_work);
+	struct qcom_jenc_dev *jenc = ctx->jenc;
+
+	mutex_lock(&jenc->dev_mutex);
+	jpeg_v4l2_work_stop(ctx, VB2_BUF_STATE_ERROR);
+	mutex_unlock(&jenc->dev_mutex);
+}
+
+static void jpeg_v4l2_work_done(struct jenc_context *ctx, size_t out_size)
+{
+	struct vb2_v4l2_buffer *vb;
+
+	vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+	if (vb)
+		v4l2_m2m_buf_done(vb, VB2_BUF_STATE_DONE);
+
+	vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+	if (vb) {
+		vb2_set_plane_payload(&vb->vb2_buf, 0, out_size);
+		v4l2_m2m_buf_done(vb, VB2_BUF_STATE_DONE);
+	}
+
+	schedule_work(&ctx->finish_work);
+}
+
+static void jpeg_v4l2_work_stop(struct jenc_context *ctx, enum vb2_buffer_state buff_state)
+{
+	struct vb2_v4l2_buffer *vb;
+
+	while ((vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
+		if (!v4l2_m2m_last_dst_buf(ctx->fh.m2m_ctx)) {
+			if (ctx->is_stopping) {
+				ctx->is_stopping = false;
+				vb2_set_plane_payload(&vb->vb2_buf, 0, 0);
+			}
+			v4l2_m2m_last_buffer_done(ctx->fh.m2m_ctx, vb);
+		} else {
+			v4l2_m2m_buf_done(vb, buff_state);
+		}
+	}
+
+	while ((vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+		v4l2_m2m_buf_done(vb, buff_state);
+
+	schedule_work(&ctx->finish_work);
+}
+
+static void jpeg_v4l2_process_cb(void *priv, enum vb2_buffer_state ev, size_t out_size)
+{
+	struct jenc_context *ctx = priv;
+	struct qcom_jenc_dev *jenc = ctx->jenc;
+
+	/* Threaded IRQ path. */
+	mutex_lock(&jenc->dev_mutex);
+
+	if (ev == VB2_BUF_STATE_DONE && out_size)
+		jpeg_v4l2_work_done(ctx, out_size);
+	else
+		jpeg_v4l2_work_stop(ctx, ev);
+
+	mutex_unlock(&jenc->dev_mutex);
+}
+
+static int cop_jpeg_v4l2_set_ctrls(struct v4l2_ctrl *ctrl)
+{
+	struct jenc_context *ectx = container_of(ctrl->handler, struct jenc_context, ctrl_hdl);
+
+	switch (ctrl->id) {
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		mutex_lock(&ectx->quality_mutex);
+		ectx->quality_requested = ctrl->val;
+		mutex_unlock(&ectx->quality_mutex);
+		break;
+	default:
+		dev_err(ectx->dev, "%s: invalid control=%#x\n", __func__, ctrl->id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops qcom_jpeg_v4l2_ctrl_ops = {
+	.s_ctrl = cop_jpeg_v4l2_set_ctrls,
+};
+
+static int bop_jpeg_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+				    unsigned int *plns_per_buff, unsigned int sizes[],
+				    struct device *alloc_devs[])
+{
+	struct jenc_context *ectx = vb2_get_drv_priv(vq);
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+	struct qcom_jenc_queue *q;
+	int pln;
+
+	q = hw->get_queue(ectx, TYPE2QID(vq->type));
+	if (!q || !q->vf.num_planes)
+		return -EINVAL;
+
+	if (*plns_per_buff) {
+		if (*plns_per_buff != q->vf.num_planes)
+			return -EINVAL;
+
+		for (pln = 0; pln < q->vf.num_planes; ++pln) {
+			if (sizes[pln] < q->vf.plane_fmt[pln].sizeimage)
+				return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	*plns_per_buff = q->vf.num_planes;
+	for (pln = 0; pln < q->vf.num_planes; ++pln) {
+		sizes[pln] = q->vf.plane_fmt[pln].sizeimage;
+		dev_dbg(ectx->dev, "%s: queue=%d size[%d]=%d\n", __func__, TYPE2QID(vq->type),
+			pln, sizes[pln]);
+	}
+
+	if (V4L2_TYPE_IS_CAPTURE(vq->type))
+		sizes[0] += JPEG_HEADER_MAX;
+
+	return hw->queue_setup(ectx, TYPE2QID(vq->type));
+}
+
+static int bop_jpeg_vb2_buf_out_validate(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+	if (vbuf->field == V4L2_FIELD_ANY)
+		vbuf->field = V4L2_FIELD_NONE;
+
+	if (vbuf->field != V4L2_FIELD_NONE)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int bop_jpeg_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct jenc_context *ectx = vb2_get_drv_priv(vb->vb2_queue);
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+	struct qcom_jenc_queue *q = &ectx->bufq[TYPE2QID(vb->type)];
+	int pln;
+	int rc;
+
+	if (vb->num_planes != q->vf.num_planes)
+		return -EINVAL;
+
+	for (pln = 0; pln < q->vf.num_planes; pln++) {
+		if (q->vf.plane_fmt[pln].sizeimage == 0)
+			return -EINVAL;
+
+		if (vb2_plane_size(vb, pln) < q->vf.plane_fmt[pln].sizeimage)
+			return -EINVAL;
+	}
+
+	rc = hw->buf_prepare(ectx, vb);
+	if (rc) {
+		dev_err_ratelimited(ectx->dev, "buffer prepare failed\n");
+		jpeg_v4l2_process_cb(ectx, VB2_BUF_STATE_ERROR, 0);
+		return rc;
+	}
+
+	return 0;
+}
+
+static void bop_jpeg_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct jenc_context *ectx = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+	v4l2_m2m_buf_queue(ectx->fh.m2m_ctx, vbuf);
+}
+
+static int bop_jpeg_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+	struct jenc_context *ectx = vb2_get_drv_priv(q);
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+	struct qcom_jenc_queue *sq = jpeg_get_bufq(ectx, JENC_SRC_QUEUE);
+	struct qcom_jenc_queue *dq = jpeg_get_bufq(ectx, JENC_DST_QUEUE);
+	u32 hw_caps;
+	u8 pln;
+	int rc;
+
+	if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+		dev_dbg(ectx->dev, "%c%c%c%c %dx%d\n",
+			(sq->vf.pixelformat >>  0) & 0xff,
+			(sq->vf.pixelformat >>  8) & 0xff,
+			(sq->vf.pixelformat >> 16) & 0xff,
+			(sq->vf.pixelformat >> 24) & 0xff,
+			sq->vf.width, sq->vf.height);
+
+		for (pln = 0; pln < sq->vf.num_planes; pln++) {
+			dev_dbg(ectx->dev, "\tpln=%d %dx%d bpl:%d size:%d\n", pln,
+				sq->vf.width, sq->vf.height,
+				sq->vf.plane_fmt[pln].bytesperline,
+				sq->vf.plane_fmt[pln].sizeimage);
+		}
+	} else {
+		dev_dbg(ectx->dev, "%c%c%c%c %dx%d\n",
+			(dq->vf.pixelformat >>  0) & 0xff,
+			(dq->vf.pixelformat >>  8) & 0xff,
+			(dq->vf.pixelformat >> 16) & 0xff,
+			(dq->vf.pixelformat >> 24) & 0xff,
+			dq->vf.width, dq->vf.height);
+	}
+
+	mutex_lock(&jenc->dev_mutex);
+
+	/*
+	 * Header cache is initialized lazily on CAPTURE QBUF, so valid V4L2
+	 * orders like STREAMON before first QBUF still get a proper JPEG header.
+	 */
+	rc = hw->hw_acquire(ectx, q);
+	if (!rc) {
+		hw->hw_get_cap(jenc, &hw_caps);
+		dev_dbg(ectx->dev, "hw_caps=0x%x\n", hw_caps);
+	}
+
+	mutex_unlock(&jenc->dev_mutex);
+
+	return rc;
+}
+
+static void bop_jpeg_vb2_stop_streaming(struct vb2_queue *q)
+{
+	struct jenc_context *ectx = vb2_get_drv_priv(q);
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+
+	mutex_lock(&jenc->dev_mutex);
+
+	jpeg_v4l2_work_stop(ectx, VB2_BUF_STATE_ERROR);
+
+	hw->hw_release(ectx, q);
+
+	mutex_unlock(&jenc->dev_mutex);
+}
+
+static const struct vb2_ops qcom_jpeg_v4l2_vb2_ops = {
+	.queue_setup		= bop_jpeg_vb2_queue_setup,
+	.buf_out_validate	= bop_jpeg_vb2_buf_out_validate,
+	.buf_prepare		= bop_jpeg_vb2_buf_prepare,
+	.buf_queue		= bop_jpeg_vb2_buf_queue,
+	.start_streaming	= bop_jpeg_vb2_start_streaming,
+	.stop_streaming		= bop_jpeg_vb2_stop_streaming,
+};
+
+static void mop_jpeg_m2m_job_abort(void *priv)
+{
+	struct jenc_context *ectx = priv;
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+
+	mutex_lock(&jenc->dev_mutex);
+
+	jpeg_v4l2_work_stop(ectx, VB2_BUF_STATE_ERROR);
+
+	mutex_unlock(&jenc->dev_mutex);
+}
+
+static void mop_jpeg_m2m_job_run(void *priv)
+{
+	struct jenc_context *ectx = priv;
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	const struct qcom_jpeg_hw_ops *hw = jenc->res->hw_ops;
+	struct vb2_v4l2_buffer *src_vb, *dst_vb;
+	struct qcom_jenc_queue *sq, *dq;
+
+	mutex_lock(&jenc->dev_mutex);
+
+	src_vb = v4l2_m2m_next_src_buf(ectx->fh.m2m_ctx);
+	dst_vb = v4l2_m2m_next_dst_buf(ectx->fh.m2m_ctx);
+
+	if (!src_vb || !dst_vb)
+		goto err_stop;
+
+	if (hw->hw_prepare(jenc))
+		goto err_stop;
+
+	sq = hw->get_queue(ectx, TYPE2QID(src_vb->vb2_buf.type));
+	src_vb->sequence = sq->sequence++;
+	if (hw->process_exec(jenc, ectx, &src_vb->vb2_buf))
+		goto err_stop;
+
+	dq = hw->get_queue(ectx, TYPE2QID(dst_vb->vb2_buf.type));
+	dst_vb->sequence = dq->sequence++;
+	if (hw->process_exec(jenc, ectx, &dst_vb->vb2_buf))
+		goto err_stop;
+
+	v4l2_m2m_buf_copy_metadata(src_vb, dst_vb);
+
+	mutex_unlock(&jenc->dev_mutex);
+	return;
+
+err_stop:
+	jpeg_v4l2_work_stop(ectx, VB2_BUF_STATE_ERROR);
+	mutex_unlock(&jenc->dev_mutex);
+}
+
+static const struct v4l2_m2m_ops qcom_jpeg_v4l2_m2m_ops = {
+	.device_run	= mop_jpeg_m2m_job_run,
+	.job_abort	= mop_jpeg_m2m_job_abort,
+};
+
+static int iop_jpeg_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, QCOM_JPEG_ENC_NAME, sizeof(cap->driver));
+	strscpy(cap->card, QCOM_JPEG_ENC_NAME, sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", QCOM_JPEG_ENC_NAME);
+
+	return 0;
+}
+
+static int iop_jpeg_enum_fmt_vid_dst(struct file *file, void *priv, struct v4l2_fmtdesc *f)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+
+	if (jpeg_is_invalid_dst(ectx, f->type))
+		return -EINVAL;
+
+	return jpeg_enum_fmt_dst(f);
+}
+
+static int iop_jpeg_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize)
+{
+	const struct jenc_enc_format *efmt;
+
+	if (fsize->index != 0)
+		return -EINVAL;
+
+	efmt = jpeg_find_pix_format(JENC_SRC_QUEUE, fsize->pixel_format);
+	if (efmt) {
+		fsize->type	= jpeg_def_frmsize.type;
+		fsize->stepwise	= jpeg_def_frmsize.stepwise;
+		return 0;
+	}
+
+	efmt = jpeg_find_pix_format(JENC_DST_QUEUE, fsize->pixel_format);
+	if (efmt) {
+		fsize->type	= jpeg_def_frmsize.type;
+		fsize->stepwise	= jpeg_def_frmsize.stepwise;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int iop_jpeg_enum_fmt_vid_src(struct file *file, void *priv, struct v4l2_fmtdesc *f)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+
+	if (jpeg_is_invalid_src(ectx, f->type))
+		return -EINVAL;
+
+	return jpeg_enum_fmt_src(f);
+}
+
+static int iop_jpeg_get_fmt_vid_dst(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+
+	if (jpeg_is_invalid_dst(ectx, f->type))
+		return -EINVAL;
+
+	jpeg_v4l2_get_format(ectx, f);
+
+	return 0;
+}
+
+static int iop_jpeg_try_fmt_vid_dst(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+
+	if (jpeg_is_invalid_dst(ectx, f->type))
+		return -EINVAL;
+
+	return jpeg_v4l2_try_format(ectx, f);
+}
+
+static int iop_jpeg_set_fmt_vid_dst(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+
+	if (jpeg_is_invalid_dst(ectx, f->type))
+		return -EINVAL;
+
+	return jpeg_v4l2_set_format(ectx, f);
+}
+
+static int iop_jpeg_get_fmt_vid_src(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+
+	if (jpeg_is_invalid_src(ectx, f->type))
+		return -EINVAL;
+
+	jpeg_v4l2_get_format(ectx, f);
+
+	return 0;
+}
+
+static int iop_jpeg_try_fmt_vid_src(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+
+	if (jpeg_is_invalid_src(ectx, f->type))
+		return -EINVAL;
+
+	return jpeg_v4l2_try_format(ectx, f);
+}
+
+static int iop_jpeg_set_fmt_vid_src(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+
+	if (jpeg_is_invalid_src(ectx, f->type))
+		return -EINVAL;
+
+	return jpeg_v4l2_set_format(ectx, f);
+}
+
+static int iop_jpeg_encoder_command(struct file *file, void *priv, struct v4l2_encoder_cmd *ec)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+	struct vb2_queue *vq;
+	int rc;
+
+	if (ec->cmd == V4L2_ENC_CMD_STOP) {
+		vq = v4l2_m2m_get_src_vq(ectx->fh.m2m_ctx);
+		if (!vb2_is_streaming(vq))
+			return 0;
+
+		vq = v4l2_m2m_get_dst_vq(ectx->fh.m2m_ctx);
+		if (!vb2_is_streaming(vq))
+			return 0;
+
+		rc = v4l2_m2m_ioctl_encoder_cmd(file, priv, ec);
+		if (rc)
+			return rc;
+
+		ectx->is_stopping = true;
+		schedule_work(&ectx->stop_work);
+
+		return 0;
+	}
+
+	return v4l2_m2m_ioctl_encoder_cmd(file, priv, ec);
+}
+
+static const struct v4l2_ioctl_ops qcom_jpeg_v4l2_ioctl_ops = {
+	.vidioc_querycap		= iop_jpeg_querycap,
+	.vidioc_enum_fmt_vid_cap	= iop_jpeg_enum_fmt_vid_dst,
+	.vidioc_enum_fmt_vid_out	= iop_jpeg_enum_fmt_vid_src,
+	.vidioc_enum_framesizes		= iop_jpeg_enum_framesizes,
+
+	.vidioc_g_fmt_vid_cap_mplane	= iop_jpeg_get_fmt_vid_dst,
+	.vidioc_try_fmt_vid_cap_mplane	= iop_jpeg_try_fmt_vid_dst,
+	.vidioc_s_fmt_vid_cap_mplane	= iop_jpeg_set_fmt_vid_dst,
+	.vidioc_g_fmt_vid_out_mplane	= iop_jpeg_get_fmt_vid_src,
+	.vidioc_try_fmt_vid_out_mplane	= iop_jpeg_try_fmt_vid_src,
+	.vidioc_s_fmt_vid_out_mplane	= iop_jpeg_set_fmt_vid_src,
+
+	.vidioc_reqbufs			= v4l2_m2m_ioctl_reqbufs,
+	.vidioc_querybuf		= v4l2_m2m_ioctl_querybuf,
+	.vidioc_prepare_buf		= v4l2_m2m_ioctl_prepare_buf,
+	.vidioc_create_bufs		= v4l2_m2m_ioctl_create_bufs,
+	.vidioc_streamon		= v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff		= v4l2_m2m_ioctl_streamoff,
+	.vidioc_qbuf			= v4l2_m2m_ioctl_qbuf,
+	.vidioc_dqbuf			= v4l2_m2m_ioctl_dqbuf,
+	.vidioc_expbuf			= v4l2_m2m_ioctl_expbuf,
+
+	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
+
+	.vidioc_encoder_cmd		= iop_jpeg_encoder_command,
+	.vidioc_try_encoder_cmd		= v4l2_m2m_ioctl_try_encoder_cmd,
+};
+
+static int jpeg_v4l2_init_queue(void *priv, struct vb2_queue *sq, struct vb2_queue *dq)
+{
+	struct jenc_context *ectx = priv;
+	int rc;
+
+	sq->drv_priv		= ectx;
+	sq->dev			= ectx->dev;
+	sq->type		= V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+	sq->io_modes		= VB2_MMAP | VB2_DMABUF | VB2_USERPTR;
+	sq->buf_struct_size	= sizeof(struct v4l2_m2m_buffer);
+	sq->ops			= &qcom_jpeg_v4l2_vb2_ops;
+	sq->mem_ops		= &vb2_dma_sg_memops;
+	sq->timestamp_flags	= V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	sq->lock		= &ectx->ctx_lock;
+	sq->min_queued_buffers	= 1;
+
+	rc = vb2_queue_init(sq);
+	if (rc)
+		return rc;
+
+	dq->drv_priv		= ectx;
+	dq->dev			= ectx->dev;
+	dq->type		= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	dq->io_modes		= VB2_MMAP | VB2_DMABUF | VB2_USERPTR;
+	dq->buf_struct_size	= sizeof(struct v4l2_m2m_buffer);
+	dq->ops			= &qcom_jpeg_v4l2_vb2_ops;
+	dq->mem_ops		= &vb2_dma_sg_memops;
+	dq->timestamp_flags	= V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dq->lock		= &ectx->ctx_lock;
+	dq->min_queued_buffers	= 1;
+
+	rc = vb2_queue_init(dq);
+	if (rc) {
+		vb2_queue_release(sq);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int fop_jpeg_file_open(struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct qcom_jenc_dev *jenc = video_drvdata(file);
+	struct jenc_context *ectx;
+	int rc;
+
+	ectx = kzalloc_obj(*ectx, GFP_KERNEL);
+	if (!ectx)
+		return -ENOMEM;
+
+	ectx->dev  = jenc->dev;
+	ectx->jenc = jenc;
+
+	/* Default quality if userspace does not set the control explicitly. */
+	ectx->quality_requested = QCOM_JPEG_QUALITY_DEF;
+	ectx->quality_programmed = 0;
+
+	mutex_init(&ectx->ctx_lock);
+	mutex_init(&ectx->quality_mutex);
+
+	INIT_WORK(&ectx->finish_work, jpeg_finish_work);
+	INIT_WORK(&ectx->stop_work, jpeg_stop_work);
+
+	rc = jpeg_v4l2_set_defaults(ectx);
+	if (rc)
+		goto err_unlock_free;
+
+	v4l2_fh_init(&ectx->fh, vdev);
+	v4l2_fh_add(&ectx->fh, file);
+
+	v4l2_ctrl_handler_init(&ectx->ctrl_hdl, 1);
+	ectx->quality_ctl = v4l2_ctrl_new_std(&ectx->ctrl_hdl,
+					      &qcom_jpeg_v4l2_ctrl_ops,
+					      V4L2_CID_JPEG_COMPRESSION_QUALITY,
+					      QCOM_JPEG_QUALITY_MIN,
+					      QCOM_JPEG_QUALITY_MAX,
+					      QCOM_JPEG_QUALITY_UNT,
+					      QCOM_JPEG_QUALITY_DEF);
+	if (ectx->ctrl_hdl.error) {
+		rc = ectx->ctrl_hdl.error;
+		goto err_fh_exit;
+	}
+
+	ectx->fh.ctrl_handler = &ectx->ctrl_hdl;
+
+	rc = v4l2_ctrl_handler_setup(&ectx->ctrl_hdl);
+	if (rc)
+		goto err_ctrl_handler_free;
+
+	v4l2_m2m_get(jenc->m2m_dev);
+	ectx->fh.m2m_ctx = v4l2_m2m_ctx_init(jenc->m2m_dev, ectx, &jpeg_v4l2_init_queue);
+	if (IS_ERR(ectx->fh.m2m_ctx)) {
+		rc = PTR_ERR(ectx->fh.m2m_ctx);
+		v4l2_m2m_put(jenc->m2m_dev);
+		goto err_ctrl_handler_free;
+	}
+
+	return 0;
+
+err_ctrl_handler_free:
+	v4l2_ctrl_handler_free(&ectx->ctrl_hdl);
+err_fh_exit:
+	v4l2_fh_del(&ectx->fh, file);
+	v4l2_fh_exit(&ectx->fh);
+err_unlock_free:
+
+	kfree(ectx);
+
+	return rc;
+}
+
+static int fop_jpeg_file_release(struct file *file)
+{
+	struct jenc_context *ectx = jpeg_file2ctx(file);
+	struct v4l2_m2m_dev *m2m_dev = ectx->fh.m2m_ctx->m2m_dev;
+
+	cancel_work_sync(&ectx->stop_work);
+	cancel_work_sync(&ectx->finish_work);
+
+	v4l2_m2m_ctx_release(ectx->fh.m2m_ctx);
+	v4l2_m2m_put(m2m_dev);
+	v4l2_ctrl_handler_free(&ectx->ctrl_hdl);
+	v4l2_fh_del(&ectx->fh, file);
+	v4l2_fh_exit(&ectx->fh);
+	kfree(ectx);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations qcom_jpeg_v4l2_file_ops = {
+	.owner		= THIS_MODULE,
+	.open		= fop_jpeg_file_open,
+	.release	= fop_jpeg_file_release,
+	.poll		= v4l2_m2m_fop_poll,
+	.mmap		= v4l2_m2m_fop_mmap,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+int qcom_jpeg_v4l2_register(struct qcom_jenc_dev *jenc)
+{
+	int rc;
+
+	mutex_lock(&jenc->dev_mutex);
+
+	jenc->enc_hw_irq_cb = jpeg_v4l2_process_cb;
+
+	jenc->m2m_dev = v4l2_m2m_init(&qcom_jpeg_v4l2_m2m_ops);
+	if (IS_ERR(jenc->m2m_dev)) {
+		dev_err(jenc->dev, "failed to init mem2mem device\n");
+		rc = PTR_ERR(jenc->m2m_dev);
+		goto err_mutex_unlock;
+	}
+
+	jenc->vdev = video_device_alloc();
+	if (!jenc->vdev) {
+		rc = -ENOMEM;
+		goto err_video_device_release;
+	}
+
+	snprintf(jenc->vdev->name, sizeof(jenc->vdev->name), "%s", QCOM_JPEG_ENC_NAME);
+	jenc->vdev->fops	= &qcom_jpeg_v4l2_file_ops;
+	jenc->vdev->ioctl_ops	= &qcom_jpeg_v4l2_ioctl_ops;
+	jenc->vdev->minor	= -1;
+	jenc->vdev->release	= video_device_release;
+	jenc->vdev->lock	= &jenc->dev_mutex;
+	jenc->vdev->v4l2_dev	= &jenc->v4l2_dev;
+	jenc->vdev->vfl_dir	= VFL_DIR_M2M;
+	jenc->vdev->device_caps	= V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
+
+	rc = video_register_device(jenc->vdev, VFL_TYPE_VIDEO, -1);
+	if (rc) {
+		dev_err(jenc->dev, "failed to register video device\n");
+		goto err_video_device_release;
+	}
+
+	video_set_drvdata(jenc->vdev, jenc);
+
+	mutex_unlock(&jenc->dev_mutex);
+
+	dev_dbg(jenc->dev, "device registered as /dev/video%d\n", jenc->vdev->num);
+
+	return rc;
+
+err_video_device_release:
+	if (jenc->vdev)
+		video_device_release(jenc->vdev);
+	v4l2_m2m_release(jenc->m2m_dev);
+err_mutex_unlock:
+	mutex_unlock(&jenc->dev_mutex);
+
+	return rc;
+}
+
+void qcom_jpeg_v4l2_unregister(struct qcom_jenc_dev *jenc)
+{
+	mutex_lock(&jenc->dev_mutex);
+
+	video_unregister_device(jenc->vdev);
+
+	v4l2_m2m_release(jenc->m2m_dev);
+
+	mutex_unlock(&jenc->dev_mutex);
+}
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h
new file mode 100644
index 000000000000..06af818e4ac9
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef QCOM_JENC_V4L2_H
+#define QCOM_JENC_V4L2_H
+
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+
+struct qcom_jenc_dev;
+
+int qcom_jpeg_v4l2_register(struct qcom_jenc_dev *jenc);
+
+void qcom_jpeg_v4l2_unregister(struct qcom_jenc_dev *jenc);
+
+#endif /* QCOM_JENC_V4L2_H */
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 3/4] arm64: dts: qcom: sm8250: Add JPEG encoder node
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
  To: linux-media
  Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel, atanas.filipov
In-Reply-To: <20260612194417.1737009-1-atanas.filipov@oss.qualcomm.com>

Add the missing JPEG encoder hardware node in SM8250 DTS so the
new qcom-jpeg V4L2 encoder driver can bind and operate on this
platform.

The node wires the resources expected by the binding and driver,
including clocks, power domain, IOMMUs and interconnect paths.

Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
 arch/arm64/boot/dts/qcom/sm8250.dtsi | 35 ++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/sm8250.dtsi b/arch/arm64/boot/dts/qcom/sm8250.dtsi
index 7076720413ab..081f172018ae 100644
--- a/arch/arm64/boot/dts/qcom/sm8250.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi
@@ -4469,6 +4469,41 @@ cci1_i2c1: i2c-bus@1 {
 			};
 		};
 
+		qcom_jpeg_enc: jpeg-encoder@ac53000 {
+			compatible = "qcom,sm8250-jenc";
+
+			reg = <0 0xac53000 0 0x1000>;
+			reg-names = "jpeg";
+
+			interrupts = <GIC_SPI 474 IRQ_TYPE_EDGE_RISING>;
+			power-domains = <&camcc TITAN_TOP_GDSC>;
+
+			clocks = <&gcc GCC_CAMERA_HF_AXI_CLK>,
+				 <&gcc GCC_CAMERA_SF_AXI_CLK>,
+				 <&camcc CAM_CC_CORE_AHB_CLK>,
+				 <&camcc CAM_CC_CPAS_AHB_CLK>,
+				 <&camcc CAM_CC_CAMNOC_AXI_CLK>,
+				 <&camcc CAM_CC_JPEG_CLK>;
+
+			clock-names = "gcc_hf_axi",
+				      "gcc_sf_axi",
+				      "core_ahb",
+				      "cpas_ahb",
+				      "camnoc_axi",
+				      "jpeg";
+			iommus = <&apps_smmu 0x2040 0x400>,
+				 <&apps_smmu 0x2440 0x400>;
+
+			interconnects = <&gem_noc MASTER_AMPSS_M0 0 &config_noc SLAVE_CAMERA_CFG 0>,
+					<&mmss_noc MASTER_CAMNOC_HF 0 &mc_virt SLAVE_EBI_CH0 0>,
+					<&mmss_noc MASTER_CAMNOC_SF 0 &mc_virt SLAVE_EBI_CH0 0>,
+					<&mmss_noc MASTER_CAMNOC_ICP 0 &mc_virt SLAVE_EBI_CH0 0>;
+			interconnect-names = "cam_ahb",
+					     "cam_hf_0_mnoc",
+					     "cam_sf_0_mnoc",
+					     "cam_sf_icp_mnoc";
+		};
+
 		camss: camss@ac6a000 {
 			compatible = "qcom,sm8250-camss";
 			status = "disabled";
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 2/4] arm64: dts: qcom: sm8550: Add JPEG encoder node
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
  To: linux-media
  Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel, atanas.filipov
In-Reply-To: <20260612194417.1737009-1-atanas.filipov@oss.qualcomm.com>

Add the missing JPEG encoder hardware node in SM8550 DTS so the
new qcom-jpeg V4L2 encoder driver can bind and operate on this
platform.

The node wires the resources expected by the binding and driver,
including clocks, power domain, IOMMUs and interconnect paths.

Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
 arch/arm64/boot/dts/qcom/sm8550.dtsi | 42 ++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/sm8550.dtsi b/arch/arm64/boot/dts/qcom/sm8550.dtsi
index 912525e9bca6..8090b8b1d7bd 100644
--- a/arch/arm64/boot/dts/qcom/sm8550.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8550.dtsi
@@ -3677,6 +3677,48 @@ port@7 {
 			};
 		};
 
+		qcom_jpeg_enc: jpeg-encoder@ac4e000 {
+			cell-index = <0>;
+			compatible = "qcom,sm8550-jenc";
+
+			reg = <0 0xac4e000 0 0x4000>;
+			reg-names = "jpeg";
+
+			interrupts = <GIC_SPI 474 IRQ_TYPE_EDGE_RISING 0>;
+			power-domains = <&camcc CAM_CC_TITAN_TOP_GDSC>;
+
+			clocks = <&gcc GCC_CAMERA_HF_AXI_CLK>,
+				 <&gcc GCC_CAMERA_SF_AXI_CLK>,
+				 <&camcc CAM_CC_CORE_AHB_CLK>,
+				 <&camcc CAM_CC_CPAS_AHB_CLK>,
+				 <&camcc CAM_CC_CAMNOC_AXI_CLK>,
+				 <&camcc CAM_CC_JPEG_CLK>;
+
+			clock-names = "gcc_hf_axi",
+				      "gcc_sf_axi",
+				      "core_ahb",
+				      "cpas_ahb",
+				      "camnoc_axi",
+				      "jpeg";
+
+			iommus = <&apps_smmu 0x20C0 0x20>,
+				 <&apps_smmu 0x20E0 0x20>;
+
+			interconnects = <&gem_noc MASTER_APPSS_PROC 0
+					 &config_noc SLAVE_CAMERA_CFG 0>,
+					<&mmss_noc MASTER_CAMNOC_HF 0
+					 &mc_virt SLAVE_EBI1 0>,
+					<&mmss_noc MASTER_CAMNOC_SF 0
+					 &mc_virt SLAVE_EBI1 0>,
+					<&mmss_noc MASTER_CAMNOC_ICP 0
+					 &mc_virt SLAVE_EBI1 0>;
+
+			interconnect-names = "cam_ahb",
+					     "cam_hf_0_mnoc",
+					     "cam_sf_0_mnoc",
+					     "cam_sf_icp_mnoc";
+		};
+
 		camcc: clock-controller@ade0000 {
 			compatible = "qcom,sm8550-camcc";
 			reg = <0 0x0ade0000 0 0x20000>;
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 1/4] dt-bindings: media: qcom: Add JPEG encoder binding
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
  To: linux-media
  Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel, atanas.filipov
In-Reply-To: <20260612194417.1737009-1-atanas.filipov@oss.qualcomm.com>

Add device-tree binding for the standalone Qualcomm JPEG encoder
hardware block (separate from CAMSS media pipelines).

Document required resources briefly and scope initial support to
currently used compatibles in this series, including SM8250,
QCM6490, and SM8550 class platforms.

Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
 .../bindings/media/qcom,jpeg-encoder.yaml     | 142 ++++++++++++++++++
 1 file changed, 142 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml

diff --git a/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
new file mode 100644
index 000000000000..83f19719a869
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
@@ -0,0 +1,142 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/qcom,jpeg-encoder.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm JPEG Encoder
+
+maintainers:
+  - Azam Sadiq Pasha Kapatrala Syed <akapatra@quicinc.com>
+  - Hariram Purushothaman <hariramp@quicinc.com>
+
+description: |
+  Qualcomm JPEG Encoder is the JPEG encode hardware present in Qualcomm SoCs.
+
+  The JPEG hardware IP version 4.2.0 is shared across SM8250, QCM6490, SM8550
+  and related derivatives.  Platforms based on the SM8550 register layout and
+  interrupt wiring can reuse the qcom,sm8550-jenc fallback compatible, provided
+  the correct register base address and IOMMU stream IDs are supplied in the
+  DTS node.
+
+properties:
+  compatible:
+    oneOf:
+      - items:
+          - const: qcom,sm8250-jenc
+      - items:
+          - enum:
+              - qcom,qcm6490-jenc
+          - const: qcom,sm8550-jenc
+      - items:
+          - const: qcom,sm8550-jenc
+
+  cell-index:
+    description: Legacy JPEG HW instance index.
+    $ref: /schemas/types.yaml#/definitions/uint32
+
+  reg:
+    maxItems: 1
+
+  reg-names:
+    oneOf:
+      - items:
+          - const: jpeg
+      - items:
+          - const: jpeg-regs
+      - items:
+          - const: jpeg_regs
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    minItems: 6
+    maxItems: 6
+
+  clock-names:
+    oneOf:
+      - items:
+          - const: gcc_hf_axi
+          - const: gcc_sf_axi
+          - const: core_ahb
+          - const: cpas_ahb
+          - const: camnoc_axi
+          - const: jpeg
+      - items:
+          - const: gcc_hf_axi_clk
+          - const: gcc_sf_axi_clk
+          - const: core_ahb_clk
+          - const: cpas_ahb_clk
+          - const: camnoc_axi_clk
+          - const: jpeg_clk
+
+  power-domains:
+    maxItems: 1
+
+  iommus:
+    maxItems: 2
+
+  interconnects:
+    maxItems: 4
+
+  interconnect-names:
+    items:
+      - const: cam_ahb
+      - const: cam_hf_0_mnoc
+      - const: cam_sf_0_mnoc
+      - const: cam_sf_icp_mnoc
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - interrupts
+  - power-domains
+  - iommus
+  - interconnects
+  - interconnect-names
+
+additionalProperties: false
+
+examples:
+  - |
+    jpeg-encoder@ac53000 {
+        compatible = "qcom,sm8250-jenc";
+        reg = <0xac53000 0x1000>;
+        reg-names = "jpeg";
+
+        interrupts = <0 474 4>;
+        power-domains = <&camcc 0>;
+
+        clock-names =
+            "gcc_hf_axi",
+            "gcc_sf_axi",
+            "core_ahb",
+            "cpas_ahb",
+            "camnoc_axi",
+            "jpeg";
+
+        clocks =
+            <&gcc 0>,
+            <&gcc 1>,
+            <&camcc 0>,
+            <&camcc 1>,
+            <&camcc 2>,
+            <&camcc 3>;
+
+        iommus = <&apps_smmu 0x2040 0x400>,
+                 <&apps_smmu 0x2440 0x400>;
+
+        interconnects =
+            <&gem_noc 0 0 &config_noc 8 0>,
+            <&mmss_noc 0 0 &mc_virt 0 0>,
+            <&mmss_noc 1 0 &mc_virt 0 0>,
+            <&mmss_noc 2 0 &mc_virt 0 0>;
+
+        interconnect-names =
+            "cam_ahb",
+            "cam_hf_0_mnoc",
+            "cam_sf_0_mnoc",
+            "cam_sf_icp_mnoc";
+    };
-- 
2.34.1


^ permalink raw reply related

* [PATCH v1 0/4] This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder.
From: Atanas Filipov @ 2026-06-12 19:44 UTC (permalink / raw)
  To: linux-media
  Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel, atanas.filipov

The goal is to upstream a standards-based JPEG encode path using the V4L2 M2M
framework, with DT-described hardware resources and SoC DTS integration.

Patch layout:
- 1/4: devicetree bindings for the standalone Qualcomm JPEG encoder block
- 2/4: SM8550 devicetree integration
- 3/4: SM8250 devicetree integration
- 4/4: qcom-jenc V4L2 mem2mem encoder driver

Supported compatibles:
- qcom,sm8250-jenc
- qcom,qcm6490-jenc
- qcom,sm8550-jenc

Driver scope and design choices:
- uses the standard V4L2 mem2mem + vb2 workflow
- maps runtime resources from DT (clocks/interconnects/iommu/etc.)
- keeps userspace interface within existing V4L2 JPEG controls/framework
- uses runtime PM for balanced power transitions across stream/probe/remove

Functional validation (hardware):
- platforms (codename / SoC / compatible / board):
  - Kona / SM8250 (QRB5165 class) / qcom,sm8250-jenc / RB5
  - Kailua / SM8550 / qcom,sm8550-jenc / RB5 Gen2
  - Kodiak / QCM6490 (QCS6490 derivative line) / qcom,qcm6490-jenc / RB3 Gen2
- validated SoC coverage: SM8250, SM8550, QCM6490
- tested flows:
  - single-frame encode: 8192x8192 NV12 (v4l2-ctl)
  - single-frame encode: 1920x1080 NV12 (v4l2-ctl)
  - single-frame encode: 1920x1080 GREY (v4l2-ctl)
  - GStreamer NV12 pipeline to JPEG files (v4l2jpegenc)
  - GStreamer GRAY8 pipeline to MJPEG output (v4l2jpegenc)

Known limitations:
- scaling is not supported
- width and height must be aligned to 16 pixels

Atanas Filipov (4):
  dt-bindings: media: qcom: Add JPEG encoder binding
  arm64: dts: qcom: sm8550: Add JPEG encoder node
  arm64: dts: qcom: sm8250: Add JPEG encoder node
  media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder

 .../bindings/media/qcom,jpeg-encoder.yaml     |  142 ++
 arch/arm64/boot/dts/qcom/sm8250.dtsi          |   35 +
 arch/arm64/boot/dts/qcom/sm8550.dtsi          |   42 +
 drivers/media/platform/qcom/Kconfig           |    1 +
 drivers/media/platform/qcom/Makefile          |    1 +
 drivers/media/platform/qcom/jpeg/Kconfig      |   18 +
 drivers/media/platform/qcom/jpeg/Makefile     |    9 +
 .../media/platform/qcom/jpeg/qcom_jenc_defs.h |  244 +++
 .../media/platform/qcom/jpeg/qcom_jenc_dev.c  |  336 ++++
 .../media/platform/qcom/jpeg/qcom_jenc_dev.h  |  107 ++
 .../media/platform/qcom/jpeg/qcom_jenc_hdr.c  |  360 ++++
 .../media/platform/qcom/jpeg/qcom_jenc_hdr.h  |  119 ++
 .../media/platform/qcom/jpeg/qcom_jenc_ops.c  | 1658 +++++++++++++++++
 .../media/platform/qcom/jpeg/qcom_jenc_ops.h  |   52 +
 .../media/platform/qcom/jpeg/qcom_jenc_res.c  |  226 +++
 .../media/platform/qcom/jpeg/qcom_jenc_res.h  |   54 +
 .../qcom/jpeg/qcom_jenc_v420_hw_info.h        |  529 ++++++
 .../media/platform/qcom/jpeg/qcom_jenc_v4l2.c | 1109 +++++++++++
 .../media/platform/qcom/jpeg/qcom_jenc_v4l2.h |   25 +
 19 files changed, 5067 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
 create mode 100644 drivers/media/platform/qcom/jpeg/Kconfig
 create mode 100644 drivers/media/platform/qcom/jpeg/Makefile
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_ops.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_res.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_res.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
 create mode 100644 drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.h


base-commit: e7ae89a0c97ce2b68b0983cd01eda67cf373517d
-- 
2.34.1


^ permalink raw reply

* Re: [PATCH v3 6/8] media: platform: Add NXP Neoisp Image Signal Processor
From: Frank Li @ 2026-06-12 19:25 UTC (permalink / raw)
  To: Antoine Bouyer
  Cc: julien.vuillaumier, alexi.birlinger, daniel.baluta, peng.fan,
	frank.li, jacopo.mondi, laurent.pinchart, mchehab, robh, krzk+dt,
	conor+dt, michael.riesch, anthony.mcgivern, linux-media,
	linux-kernel, devicetree, imx, ai.luthra, paul.elder, geert,
	sakari.ailus, hverkuil+cisco
In-Reply-To: <20260612132039.2089051-7-antoine.bouyer@nxp.com>

On Fri, Jun 12, 2026 at 03:20:37PM +0200, Antoine Bouyer wrote:
> First NXP neoisp driver version with the following contents:
>
> This driver was initially inspired from raspberrypi pisp_be driver. It
> reuses same approach for ISP job scheduling.
>
> The Neoisp driver supports:
> * 8, 10, 12, 14 and 16-bits RAW Bayer images input.
> * Monochrome sensors input.
> * RGB/YUV, IR and Greyscale output formats.
>
> The neoisp features are:
> * Provides single context to limit amount of v4l2 devices.
> * Supports M2M operations.
> * Support SDR and HDR modes.
> * Supports generic v4l2-isp framework for extensible Parameters and
> Statistics buffers.
> * Provides a `core_media_register` API to register neoisp's media entities
> into another media graph.
> * A module parameter to run in standalone mode with its own media device.
>
> Co-developed-by: Alexi Birlinger <alexi.birlinger@nxp.com>
> Signed-off-by: Alexi Birlinger <alexi.birlinger@nxp.com>
> Signed-off-by: Antoine Bouyer <antoine.bouyer@nxp.com>
> ---
...
>
> +MEDIA DRIVERS FOR NXP NEOISP
> +M:	Antoine Bouyer <antoine.bouyer@nxp.com>
> +S:	Maintained
> +F:	Documentation/admin-guide/media/nxp-neoisp*
> +F:	Documentation/devicetree/bindings/media/nxp,imx95-neoisp.yaml
> +F:  Documentation/userspace-api/media/v4l/metafmt-nxp-neoisp.rst

Use tab.

> +F:	drivers/media/platform/nxp/neoisp/*
> +F:	include/uapi/linux/media/nxp/nxp_neoisp.h
> +
...
> +#define NEOISP_MIN_H			64U
> +#define NEOISP_MAX_W			4096U
> +#define NEOISP_MAX_H			4096U
> +#define NEOISP_MAX_BPP			4U
> +#define NEOISP_ALIGN_W			3
> +#define NEOISP_ALIGN_H			3
> +#define NEOISP_DEF_W			640U
> +#define NEOISP_DEF_H			480U

look like needn't "U" for these const.

> +
> +#define NEOISP_SUSPEND_TIMEOUT_MS	500
> +
...
> +
> +/*
> + * Extract offset and size in bytes from memory region map
> + */
> +static inline void get_offsize(enum isp_block_map_e map, u32 *offset, u32 *size)

Needn't inline. check others, compiler will auto do it and also find unused
function if no inline.

> +{
> +	*offset = ISP_GET_OFF(map);
> +	*size = ISP_GET_SZ(map);
> +}
> +
...
> +
> +static const struct v4l2_frmsize_stepwise neoisp_frmsize_stepwise = {
> +	.min_width = NEOISP_MIN_W,
> +	.min_height = NEOISP_MIN_H,
> +	.max_width = NEOISP_MAX_W,
> +	.max_height = NEOISP_MAX_H,
> +	.step_width = 1UL << NEOISP_ALIGN_W,
> +	.step_height = 1UL << NEOISP_ALIGN_H,
> +};

why put this into neoisp_fmt.h? if two c file include it, these data will
be duplicated

...
> +		bit = NEO_PIPE_CONF_SOFT_RESET_HARD_RESET;
> +
> +	neoisp_wr(neoispd, NEO_PIPE_CONF_SOFT_RESET, bit);
> +
> +	/* Wait for auto-clear */
> +	do {
> +		usleep_range(1, 2);
> +		val = neoisp_rd(neoispd, NEO_PIPE_CONF_SOFT_RESET);
> +		count--;
> +	} while ((val & bit) && count);

Use read_poll_timeout()

> +
> +	if (val & bit)
> +		dev_warn(neoispd->dev, "%s reset incomplete\n",
> +			 is_hw ? "hw" : "sw");
> +}
...
> +		/*
> +		 * Take a copy of streaming_map: nodes activated after this
> +		 * point are ignored when preparing this job
> +		 */
> +		streaming_map = neoispd->streaming_map;
> +	}
> +
> +	job = kzalloc(sizeof(*job), GFP_KERNEL);

use kzalloc_obj()

> +	if (!job)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < NEOISP_NODES_COUNT; i++) {
...
> +static irqreturn_t neoisp_irq_handler(int irq, void *dev_id)
> +{
> +	struct neoisp_dev_s *neoispd = (struct neoisp_dev_s *)dev_id;
> +	struct neoisp_buffer_s **buf = neoispd->queued_job.buf;
> +	u64 ts = ktime_get_ns();
> +	u32 irq_status = 0;
> +	u32 irq_clear = 0;
> +	bool done = false;
> +	int i;
> +
> +	irq_status = neoisp_rd(neoispd, NEO_PIPE_CONF_INT_STAT0);
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_S_FS1) {
> +		dev_dbg(neoispd->dev, "Neo IRQ FS1 !\n");
> +		irq_clear |= NEO_PIPE_CONF_INT_STAT0_S_FS1;
> +		done = false;
> +	}
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_S_FS2) {
> +		dev_dbg(neoispd->dev, "Neo IRQ FS2 !\n");
> +		irq_clear |= NEO_PIPE_CONF_INT_STAT0_S_FS2;
> +		done = false;
> +	}
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_S_FD1) {
> +		dev_dbg(neoispd->dev, "Neo IRQ FD1 !\n");
> +		irq_clear |= NEO_PIPE_CONF_INT_STAT0_S_FD1;
> +		done = false;
> +	}
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_S_STATD) {
> +		dev_dbg(neoispd->dev, "Neo IRQ STATD !\n");
> +		irq_clear |= NEO_PIPE_CONF_INT_STAT0_S_STATD;
> +		done = false;
> +	}
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_S_DRCD) {
> +		dev_dbg(neoispd->dev, "Neo IRQ DRCD !\n");
> +		neoisp_ctx_get_stats(neoispd, buf[NEOISP_STATS_NODE]);
> +		irq_clear |= NEO_PIPE_CONF_INT_STAT0_S_DRCD;
> +		done = false;
> +	}
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_S_BUS_ERR) {
> +		irq_clear |= NEO_PIPE_CONF_INT_STAT0_S_BUS_ERR;
> +		dev_err(neoispd->dev, "Neo IRQ BUS ERR!\n");
> +		done = true;
> +	}
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_S_TRIG_ERR) {
> +		dev_err(neoispd->dev, "Neo IRQ TRIG ERR !\n");
> +		irq_clear |= NEO_PIPE_CONF_INT_STAT0_S_TRIG_ERR;
> +		done = true;
> +	}
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_S_CSI_TERR) {
> +		dev_err(neoispd->dev, "Neo IRQ TRIG CSI Trigger ERR !\n");
> +		irq_clear |= NEO_PIPE_CONF_INT_STAT0_S_CSI_TERR;
> +		done = true;
> +	}
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_S_FD2) {
> +		dev_dbg(neoispd->dev, "Neo IRQ FD2 !\n");
> +		irq_clear |= NEO_PIPE_CONF_INT_STAT0_S_FD2;
> +		done = true;
> +	}
> +
> +	if (irq_status & NEO_PIPE_CONF_INT_STAT0_BUSY)
> +		dev_err(neoispd->dev, "Neo is busy !\n");
> +
> +	neoisp_wr(neoispd, NEO_PIPE_CONF_INT_STAT0, irq_clear);

Did you share irq with other device?

you can use directly
	neoisp_wr(neoispd, NEO_PIPE_CONF_INT_STAT0, irq_status);

and if there are unexpected irq happen, which is not in your check list,
it will cause irq storm.

	so need more if block

	just

	if (irq_status & (NEO_PIPE_CONF_INT_STAT0_S_BUS_ER | irq_status & NEO_PIPE_CONF_INT_STAT0_S_BUS_ER ...)
		done = true;

default done is falue.

> +
> +	if (done) {
> +		for (i = 0; i < NEOISP_NODES_COUNT; i++) {
> +			if (!buf[i])
> +				continue;
> +
> +			buf[i]->vb.sequence = neoispd->frame_sequence;
> +			buf[i]->vb.vb2_buf.timestamp = ts;
> +			vb2_buffer_done(&buf[i]->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +
> +			/* To prevent double buffer handling in case of spurius interrupt */
> +			buf[i] = NULL;
> +		}
> +		/* Update frame_sequence */
> +		neoispd->frame_sequence++;
> +		/* Check if there's more to do before going to sleep */
> +		neoisp_schedule(neoispd, true);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
...
> +static int neoisp_init_node(struct neoisp_dev_s *neoispd, u32 id)
> +{
> +	bool output = node_desc_is_output(&node_desc[id]);
> +	struct neoisp_node_s *node = &neoispd->node[id];
> +	struct media_entity *entity = &node->vfd.entity;
> +	struct media_pad *mpad;
> +	struct video_device *vdev = &node->vfd;
> +	struct vb2_queue *q = &node->queue;
> +	int ret;

please try keep revise christmas tree order for nice look.

> +
> +	node->id = id;
> +	node->neoisp = neoispd;
> +	node->buf_type = node_desc[id].buf_type;
> +
...
> +{
> +	struct v4l2_device *v4l2_dev = &neoispd->v4l2_dev;
> +	u32 num_registered = 0;
> +	int ret;
> +
> +	mutex_init(&neoispd->queue_lock);

suppose this function call from probe, so you can use devm_mutex_init()

> +
> +	/* Register v4l2_device and media_device */
> +	v4l2_dev->mdev = mdev;
> +	strscpy(v4l2_dev->name, NEOISP_NAME, sizeof(v4l2_dev->name));
...
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to request irq: %d\n", ret);
> +		goto err_irq;
> +	}
> +
> +	pm_runtime_set_autosuspend_delay(dev, NEOISP_SUSPEND_TIMEOUT_MS);
> +	pm_runtime_use_autosuspend(dev);
> +	pm_runtime_enable(dev);

devm_pm_runtime_enable();

> +	ret = pm_runtime_resume_and_get(dev);

you can use below code now, needn't goto branch

	PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm);
	if (PM_RUNTIME_ACQUIRE_ERR(&pm))
		return -ENXIO;


> +	if (ret < 0) {
> +		dev_err(dev, "Unable to resume the device: %d\n", ret);
> +		goto err_pm_runtime_disable;
> +	}
> +
> +	ret = neoisp_init_devices(neoispd);
> +	if (ret)
> +		goto err_pm_runtime_suspend;
> +
> +	spin_lock_init(&neoispd->hw_lock);
> +	neoisp_init_hw(neoispd);
> +	neoisp_set_default_context(neoispd);
> +
> +	pm_runtime_mark_last_busy(dev);

Needn't call this now

> +	pm_runtime_put_autosuspend(dev);
> +
> +	return 0;
> +
> +err_pm_runtime_suspend:
> +	pm_runtime_put(dev);
> +err_pm_runtime_disable:
> +	pm_runtime_dont_use_autosuspend(dev);
> +	pm_runtime_disable(dev);
> +err_irq:
> +	dev_err(dev, "probe: error %d\n", ret);
> +	return ret;
> +}
> +
> +static void neoisp_remove(struct platform_device *pdev)
> +{
> +	struct neoisp_dev_s *neoispd = platform_get_drvdata(pdev);
> +
> +	neoisp_destroy_devices(neoispd);
> +
> +	if (standalone_mdev)
> +		media_device_cleanup(&neoispd->mdev);
> +
> +	pm_runtime_dont_use_autosuspend(neoispd->dev);
> +	pm_runtime_disable(neoispd->dev);
> +}
> +
> +static int __maybe_unused neoisp_runtime_suspend(struct device *dev)

Needn't __maybe now

> +{
> +	struct neoisp_dev_s *neoispd = dev_get_drvdata(dev);
> +
> +	clk_bulk_disable_unprepare(neoispd->num_clks, neoispd->clks);
> +
> +	return 0;
> +}
> +
...
> +
> +static const struct dev_pm_ops neoisp_pm = {
> +	SET_SYSTEM_SLEEP_PM_OPS(neoisp_pm_suspend, neoisp_pm_resume)
> +	SET_RUNTIME_PM_OPS(neoisp_runtime_suspend, neoisp_runtime_resume, NULL)

Use new macro
RUNTIME_PM_OPS

> +};
> +
...
> +
> +static struct platform_driver neoisp_driver = {
> +	.probe  = neoisp_probe,
> +	.remove = neoisp_remove,
> +	.driver = {
> +		.name = NEOISP_NAME,
> +		.pm = &neoisp_pm,

pm_ptr(&neoisp_pm)

> +		.of_match_table = neoisp_dt_ids,
> +	},
> +};
> +
> +module_platform_driver(neoisp_driver);
> +
> +MODULE_DESCRIPTION("NXP NEOISP Hardware");
> +MODULE_AUTHOR("Antoine Bouyer <antoine.bouyer@nxp.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/media/platform/nxp/neoisp/neoisp_nodes.h b/drivers/media/platform/nxp/neoisp/neoisp_nodes.h
> new file mode 100644
> index 000000000000..54986fe13b33
> --- /dev/null
> +++ b/drivers/media/platform/nxp/neoisp/neoisp_nodes.h
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * NEOISP nodes description
> + *
> + * Copyright 2023-2026 NXP
> + */
> +
> +#ifndef __NXP_NEOISP_NODES_H
> +#define __NXP_NEOISP_NODES_H
> +
> +#include <linux/videodev2.h>
> +
> +#include "neoisp.h"
> +
> +static const struct neoisp_node_desc_s node_desc[NEOISP_NODES_COUNT] = {

These const data should not appear in header files.

Frank
>

^ permalink raw reply

* Re: [PATCH v5 1/3] dt-bindings: pwm: Add Raspberry Pi RP1 PWM controller
From: Stanimir Varbanov @ 2026-06-12 15:24 UTC (permalink / raw)
  To: Andrea della Porta, Uwe Kleine-König, linux-pwm, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Florian Fainelli,
	Broadcom internal kernel review list, devicetree,
	linux-rpi-kernel, linux-arm-kernel, linux-kernel, Naushir Patuck,
	Stanimir Varbanov, mbrugger
  Cc: Krzysztof Kozlowski
In-Reply-To: <350c2fb454951fd2c9d959f1d94802fea8fa8152.1780670224.git.andrea.porta@suse.com>



On 6/12/26 5:01 PM, Andrea della Porta wrote:
> From: Naushir Patuck <naush@raspberrypi.com>
> 
> Add the devicetree binding documentation for the PWM
> controller found in the Raspberry Pi RP1 chipset.
> 
> Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
> Co-developed-by: Stanimir Varbanov <svarbanov@suse.de>
> Signed-off-by: Stanimir Varbanov <svarbanov@suse.de>
> Signed-off-by: Andrea della Porta <andrea.porta@suse.com>
> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> ---
>  .../bindings/pwm/raspberrypi,rp1-pwm.yaml     | 54 +++++++++++++++++++
>  1 file changed, 54 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/pwm/raspberrypi,rp1-pwm.yaml
> 
> diff --git a/Documentation/devicetree/bindings/pwm/raspberrypi,rp1-pwm.yaml b/Documentation/devicetree/bindings/pwm/raspberrypi,rp1-pwm.yaml
> new file mode 100644
> index 0000000000000..6f8461d0454f7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/raspberrypi,rp1-pwm.yaml
> @@ -0,0 +1,54 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pwm/raspberrypi,rp1-pwm.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Raspberry Pi RP1 PWM controller
> +
> +maintainers:
> +  - Naushir Patuck <naush@raspberrypi.com>

Could you add you or me as a maintainer as well. I'm not sure Naushir
had agreed to maintain the bindings in mainline.

~Stan

^ permalink raw reply

* Re: [net-next 6/9] net: ethernet: ravb: Add callback for gPTP probe
From: Sergey Shtylyov @ 2026-06-12 18:48 UTC (permalink / raw)
  To: Niklas Söderlund, Paul Barker, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Richard Cochran,
	Geert Uytterhoeven, Magnus Damm, netdev, linux-renesas-soc,
	devicetree, linux-kernel
In-Reply-To: <20260610102432.3538432-7-niklas.soderlund+renesas@ragnatech.se>

On 6/10/26 1:24 PM, Niklas Söderlund wrote:

> Different generations of the RAVB IP have different needs when it probes
> the gPTP timer clock. Add a callback in the PTP information to allow
> each generation to probe its own way.
> 
> With this the last gPTP specific flag (gptp_ref_clk) can be removed.
> However the primary motivation for the change is to prepare for Gen4
> support, which compared to other generations with gPTP support does not
> have the clock as part of the IP itself.
> 
> Gen4 will not need to compute GTI value as it have no where to write it,

   Nowhere.

> as the gPTP clock is external. For this reason move the computation of
> it into the newly gPTP probe specific callbacks for the RAVB IP's that
> support it.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Reviewed-by: Sergey Shtylyov <sergei.shtylyov@gmail.com>

[...]

MBR, Sergey


^ permalink raw reply

* Re: [PATCH 1/2] ARM: dts: renesas: r9a06g032: Describe SPI controllers
From: Wolfram Sang @ 2026-06-12 18:43 UTC (permalink / raw)
  To: Geert Uytterhoeven
  Cc: linux-renesas-soc, Magnus Damm, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, devicetree
In-Reply-To: <CAMuHMdW6tup=MKtoJBjU1u-3QW+S4zAwrKKngMNy9bqVkgpTFg@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 248 bytes --]


> However, spi-max-frequency also depends on the target device(s),
> and on board wiring, so typically it is overridden or set in board DTS.

I'll drop them all!

Everything else is accepted, too, and already fixed. Also put "disabled"
last now.


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply

* Re: [PATCH 2/2] ARM: dts: renesas: r9a06g032-rzn1d400-eb: Enable SPI-FRAM
From: Wolfram Sang @ 2026-06-12 18:42 UTC (permalink / raw)
  To: Geert Uytterhoeven
  Cc: linux-renesas-soc, Magnus Damm, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, devicetree
In-Reply-To: <CAMuHMdWEJT8JYuSGQmNsbUZuU+zx7prwMHVikOuOS-iXYKenbw@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 1288 bytes --]

Hi Geert,

thank you for the reviews!

> > +&spi1 {
> > +       pinctrl-0 = <&pins_spi1>;
> > +       pinctrl-names = "default";
> 
> Please document that this depends on SW2-4 being OFF.

Hmmm, this is a bit weird. SW2-4 does only do the routing to the SPI5
(target) controller connector. SPI1 is totally independent of that. I
can read out the FRAM with 2-4 being ON. Dunno how well that behaves in
parallel with RMII2 ;) Shall I still do the comment?

> > +       cs-gpios = <&gpio2a 31 GPIO_ACTIVE_LOW>;
> 
> It doesn't work with hardware chip-select?

I couldn't get it to work and I know other people also use cs-gpios on
their custom boards.

> > +       fram: fram@0 {
> > +               compatible = "cypress,fm25", "atmel,at25";
> > +               reg = <0>;
> > +               spi-max-frequency = <12500000>;
> 
> The actual FRAM part seems to support 40 MHz, but that may
> be limited by the board wiring.

So, what do we do? In my tests, 40MHz did not work. Up to 30MHz it seems
to work. However, all of the BSP DTS snippets say RZ/N1 can only do up
to 12.5MHz. I don't have an errata documenting this nor could I find
another source. I would still rather play safe here. I could add a
comment, though.

Happy hacking,

   Wolfram


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply

* Re: [net-next 8/9] dt-bindings: net: renesas,etheravb: Add optional gPTP phandle for Gen4
From: Sergey Shtylyov @ 2026-06-12 18:37 UTC (permalink / raw)
  To: Niklas Söderlund, Paul Barker, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Richard Cochran,
	Geert Uytterhoeven, Magnus Damm, netdev, linux-renesas-soc,
	devicetree, linux-kernel
In-Reply-To: <20260610102432.3538432-9-niklas.soderlund+renesas@ragnatech.se>

On 6/10/26 1:24 PM, Niklas Söderlund wrote:

> The RAVB module on Gen4 have no gPTP clock as part of the RAVB module
> itself, instead it relies on an external system wide gPTP clock. The
> gPTP clock is shared with RTSN on V4H and RSWITCH on S4.
> 
> Add an optional phandle so that the RAVB driver can find and use the
> gPTP clock. Ideally this should have been an mandatory property but for

   s/an/a/.

> backward compatible it is optional. The RAVB module is capable of
> functioning without it, but can in such cases not provided PTP
> functionality.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Reviewed-by: Sergey Shtylyov <sergei.shtylyov@gmail.com>

[...]

> diff --git a/Documentation/devicetree/bindings/net/renesas,etheravb.yaml b/Documentation/devicetree/bindings/net/renesas,etheravb.yaml
> index 1e00ef5b3acd..7bc910ab3ae0 100644
> --- a/Documentation/devicetree/bindings/net/renesas,etheravb.yaml
> +++ b/Documentation/devicetree/bindings/net/renesas,etheravb.yaml
> @@ -122,6 +122,13 @@ properties:
>        Specify when the AVB_LINK signal is active-low instead of normal
>        active-high.
>  
> +  renesas,gptp:
> +    $ref: /schemas/types.yaml#/definitions/phandle
> +    description:
> +      A phandle to an external gPTP clock for Gen4 platforms. The property is

   You're sure wa can't handle that with the usual "clocks" prop?

> +      optional for backwards compatibility, but without it gPTP timestamps are
> +      disabled as Gen4 have no gPTP as part of the RAVB module itself.

   Again, I'd prefer EtherAVB -- to comply with the binding file name...

[...]

MBR, Sergey


^ permalink raw reply

* Re: [net-next 5/9] net: ethernet: ravb: Replace gPTP flags with callbacks
From: Sergey Shtylyov @ 2026-06-12 18:31 UTC (permalink / raw)
  To: Niklas Söderlund, Paul Barker, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Richard Cochran,
	Geert Uytterhoeven, Magnus Damm, netdev, linux-renesas-soc,
	devicetree, linux-kernel
In-Reply-To: <20260610102432.3538432-6-niklas.soderlund+renesas@ragnatech.se>

On 6/10/26 1:24 PM, Niklas Söderlund wrote:

> Prepare for adding Gen4 support which will add a third and new way to
> interact with the gPTP clock by replacing the flags for Gen2 behavior
> (info->gptp) and Gen3 behavior (info->ccc_gac) with callbacks.
> 
> This will make adding Gen4 support cleaner as the code will not have "if
> else if else" sprinkled all over to handle each generations special
> cases.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Reviewed-by: Sergey Shtylyov <sergei.shtylyov@gmail.com>

[...]

> diff --git a/drivers/net/ethernet/renesas/ravb.h b/drivers/net/ethernet/renesas/ravb.h
> index 013ced6dcf29..70bef3b31d38 100644
> --- a/drivers/net/ethernet/renesas/ravb.h
> +++ b/drivers/net/ethernet/renesas/ravb.h
> @@ -1034,6 +1034,27 @@ struct ravb_ptp {
>  	struct ravb_ptp_perout perout[N_PER_OUT];
>  };
>  
> +/**
> + * struct ravb_gptp_info - Platform specific gPTP behavior
> + *
> + * Each generation of RAVB have slightly different behaviors when interacting

   Well, I haven't seen the word RAVB in any Renesas' manuals, have you?
   I personally prefer calling it EtherAVB; the manuals had "Ethernet AVB", IIRC... :-)

[...]

MBR, Sergey


^ permalink raw reply

* Re: [PATCH v3 4/8] media: uapi: Add NXP NEOISP user interface header file
From: Frank Li @ 2026-06-12 18:31 UTC (permalink / raw)
  To: Antoine Bouyer
  Cc: julien.vuillaumier, alexi.birlinger, daniel.baluta, peng.fan,
	frank.li, jacopo.mondi, laurent.pinchart, mchehab, robh, krzk+dt,
	conor+dt, michael.riesch, anthony.mcgivern, linux-media,
	linux-kernel, devicetree, imx, ai.luthra, paul.elder, geert,
	sakari.ailus, hverkuil+cisco
In-Reply-To: <20260612132039.2089051-5-antoine.bouyer@nxp.com>

On Fri, Jun 12, 2026 at 03:20:35PM +0200, Antoine Bouyer wrote:
>
> Add user space api header file for meta data structures definitions.
>
> This header describes `parameters` buffer for the ISP blocks control by
> userspace, and `statistics` buffer for userspace and IPA handling.
>
> Both buffers use the newly introduced generic `v4l2_isp_buffer`
> definition, which behaves the same as the generic `v4l2_isp_params_buffer`
> already used by other ISP devices (rkisp1, mali-c55).
>
> Signed-off-by: Antoine Bouyer <antoine.bouyer@nxp.com>
> ---
...
> + */
> +struct neoisp_hdr_decompress0_cfg_s {
> +       __u16 knee_point1;
> +       __u16 knee_point2;
> +       __u16 knee_point3;
> +       __u16 knee_point4;

Not sure why don't use __u16 knee_point[4] to make code clear and short?

> +       __u16 knee_offset0;
> +       __u16 knee_offset1;
> +       __u16 knee_offset2;
> +       __u16 knee_offset3;
> +       __u16 knee_offset4;
...
> + */
> +struct neoisp_ir_compress_cfg_s {
> +       __u8 ctrl_obpp;

do we need pad some data to align next __u32

> +       __u32 knee_point1_kneepoint;
> +       __u32 knee_point2_kneepoint;
> +       __u32 knee_point3_kneepoint;
> +       __u32 knee_point4_kneepoint;
> +       __u32 knee_offset0_offset;
> +       __u32 knee_offset1_offset;
> +       __u32 knee_offset2_offset;
> +       __u32 knee_offset3_offset;
> +       __u32 knee_offset4_offset;
> +       __u16 knee_ratio01_ratio0;
> +       __u16 knee_ratio01_ratio1;
> +       __u16 knee_ratio23_ratio2;
> +       __u16 knee_ratio23_ratio3;
> +       __u16 knee_ratio4_ratio4;
> +       __u16 knee_npoint0_kneepoint;
> +       __u16 knee_npoint1_kneepoint;
> +       __u16 knee_npoint2_kneepoint;
> +       __u16 knee_npoint3_kneepoint;
> +       __u16 knee_npoint4_kneepoint;
> +};
> +
...
> +struct neoisp_bnr_cfg_s {
> +       __u8 ctrl_debug;
> +       __u8 ctrl_obpp;
> +       __u8 ctrl_nhood;
> +       __u8 ypeak_peak_outsel;
> +       __u8 ypeak_peak_sel;
> +       __u16 ypeak_peak_low;
> +       __u16 ypeak_peak_high;
> +       __u32 yedge_th0_edge_th0;
> +       __u16 yedge_scale_scale;
> +       __u8 yedge_scale_shift;
> +       __u32 yedges_th0_edge_th0;
> +       __u16 yedges_scale_scale;
> +       __u8 yedges_scale_shift;
> +       __u32 yedgea_th0_edge_th0;
> +       __u16 yedgea_scale_scale;
> +       __u8 yedgea_scale_shift;
> +       __u32 yluma_x_th0_th;
> +       __u16 yluma_y_th_luma_y_th0;
> +       __u16 yluma_y_th_luma_y_th1;
> +       __u16 yluma_scale_scale;
> +       __u8 yluma_scale_shift;
> +       __u16 yalpha_gain_gain;
> +       __u16 yalpha_gain_offset;
> +       __u8 cpeak_peak_outsel;
> +       __u8 cpeak_peak_sel;
> +       __u16 cpeak_peak_low;
> +       __u16 cpeak_peak_high;
> +       __u32 cedge_th0_edge_th0;
> +       __u16 cedge_scale_scale;
> +       __u8 cedge_scale_shift;
> +       __u32 cedges_th0_edge_th0;
> +       __u16 cedges_scale_scale;
> +       __u8 cedges_scale_shift;
> +       __u32 cedgea_th0_edge_th0;
> +       __u16 cedgea_scale_scale;
> +       __u8 cedgea_scale_shift;
> +       __u32 cluma_x_th0_th;
> +       __u16 cluma_y_th_luma_y_th0;
> +       __u16 cluma_y_th_luma_y_th1;
> +       __u16 cluma_scale_scale;
> +       __u8 cluma_scale_shift;
> +       __u16 calpha_gain_gain;
> +       __u16 calpha_gain_offset;
> +       __u16 stretch_gain;
> +};

Does have data alginment problem if use difference compiler? User APP
may difference compiler and compiler options.


^ permalink raw reply

* Re: [net-next 4/9] net: ethernet: ravb: Remove redundant argument to ravb_ptp_init()
From: Sergey Shtylyov @ 2026-06-12 18:17 UTC (permalink / raw)
  To: Niklas Söderlund, Paul Barker, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Richard Cochran,
	Geert Uytterhoeven, Magnus Damm, netdev, linux-renesas-soc,
	devicetree, linux-kernel
In-Reply-To: <20260610102432.3538432-5-niklas.soderlund+renesas@ragnatech.se>

On 6/10/26 1:24 PM, Niklas Söderlund wrote:

> There is no need to explicitly pass the struct platform_device pointer
> to ravb_ptp_init(), it can retrieve it directly from the private data
> structure.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Reviewed-by: Sergey Shtylyov <sergei.shtylyov@gmail.com>

[...]

MBR, Sergey


^ permalink raw reply

* Re: [net-next 2/9] net: ethernet: ravb: Move programming of gPTP timer interval
From: Sergey Shtylyov @ 2026-06-12 18:16 UTC (permalink / raw)
  To: Niklas Söderlund, Paul Barker, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Richard Cochran,
	Geert Uytterhoeven, Magnus Damm, netdev, linux-renesas-soc,
	devicetree, linux-kernel
In-Reply-To: <20260610102432.3538432-3-niklas.soderlund+renesas@ragnatech.se>

On 6/10/26 1:24 PM, Niklas Söderlund wrote:

> Commit f384ab481cab ("net: ravb: Split GTI computation and set
> operations") broke apart the operations of computing the timer interval
> and programming of it. However it kept the programming of the interval
> in the RAVB main logic.
> 
> Having split the two apart this can be improved further by moving the
> programming to the gPTP initialization function, as the first action of
> the gPTP init function is to wait for the timer interval programming to
> be acknowledge by the hardware.
> 
> As an added bonus the interaction with the gPTP registers for the
> programming can then also be done while holding the gPTP registers lock.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Reviewed-by: Sergey Shtylyov <sergei.shtylyov@gmail.com>

[...]

MBR, Sergey


^ permalink raw reply

* Re: [net-next 1/9] net: ethernet: ravb: Remove gPTP control from WoL setup and restore
From: Sergey Shtylyov @ 2026-06-12 18:12 UTC (permalink / raw)
  To: Niklas Söderlund, Paul Barker, Andrew Lunn, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Richard Cochran,
	Geert Uytterhoeven, Magnus Damm, netdev, linux-renesas-soc,
	devicetree, linux-kernel
In-Reply-To: <20260610102432.3538432-2-niklas.soderlund+renesas@ragnatech.se>

On 6/10/26 1:24 PM, Niklas Söderlund wrote:

> Since commit a6a85ba36fd0 ("net: ravb: Move PTP initialization in the
> driver's ndo_open API for ccc_gac platorms") the gPTP clock (if
> supported) is stopped and started by opening and closing the ndev.
> 
> This makes the special case to stop and start it when resuming from WoL
> redundant. As the ndev will always be closed and re-opened when
> suspending and resuming the system.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Reviewed-by: Sergey Shtylyov <sergei.shtylyov@gmail.com>

[...]

MBR, Sergey


^ permalink raw reply

* Re: [PATCH v3 2/3] dt-bindings: iio: flow: add Sensirion SLF3S liquid flow sensor
From: Jonathan Cameron @ 2026-06-12 18:05 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Wadim Mueller, Krzysztof Kozlowski, Rob Herring, Conor Dooley,
	David Lechner, Nuno Sá, Andy Shevchenko, Maxwell Doose,
	linux-iio, devicetree, linux-kernel
In-Reply-To: <20260607-quixotic-steel-puma-133410@quoll>

On Sun, 7 Jun 2026 10:30:07 +0200
Krzysztof Kozlowski <krzk@kernel.org> wrote:

> On Fri, Jun 05, 2026 at 01:21:35PM +0100, Jonathan Cameron wrote:
> > On Thu, 4 Jun 2026 13:22:17 +0200
> > Krzysztof Kozlowski <krzk@kernel.org> wrote:
> >   
> > > On 04/06/2026 11:03, Jonathan Cameron wrote:  
> > > > On Wed, 3 Jun 2026 16:29:10 +0200
> > > > Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > > >     
> > > >> On 01/06/2026 16:09, Jonathan Cameron wrote:    
> > > >>> On Mon, 1 Jun 2026 13:53:23 +0200
> > > >>> Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > > >>>       
> > > >>>> On Sat, May 30, 2026 at 10:54:31PM +0200, Wadim Mueller wrote:      
> > > >>>>> Document the bindings for the Sensirion SLF3S family of digital
> > > >>>>> liquid-flow sensors on I2C.  The family currently covers the
> > > >>>>> SLF3S-0600F, SLF3S-1300F and SLF3S-4000B variants.
> > > >>>>>
> > > >>>>> The driver auto-detects the variant from the product-information
> > > >>>>> register at probe time; the per-variant compatible strings exist
> > > >>>>> for documentation and dt_binding_check purposes.        
> > > >>>>
> > > >>>> Here...
> > > >>>>      
> > > >>>>> +description:
> > > >>>>> +  Family of digital liquid-flow sensors from Sensirion with I2C
> > > >>>>> +  interface.  All family members share the same register map; sub-types
> > > >>>>> +  differ only in the flow scale factor and the calibrated measurement
> > > >>>>> +  range, both of which are detected at probe time via the
> > > >>>>> +  product-information register.        
> > > >>>>
> > > >>>> And here...
> > > >>>>      
> > > >>>>> +
> > > >>>>> +properties:
> > > >>>>> +  compatible:
> > > >>>>> +    enum:
> > > >>>>> +      - sensirion,slf3s-0600f
> > > >>>>> +      - sensirion,slf3s-1300f
> > > >>>>> +      - sensirion,slf3s-4000b        
> > > >>>>
> > > >>>> And here something else. Confusing. Didn't you say device variants are
> > > >>>> auto-detectable? So you have only one compatible sensirion,slf3s.      
> > > >>>
> > > >>> And then future fallback compatibles can never work. 
> > > >>> Basically as far as I have ever been able to establish this is why
> > > >>> generic compatibles are almost always the wrong way to go.
> > > >>>
> > > >>> If we get a future part with an unknown ID and don't have these existing
> > > >>> specific compatibles, then we have no way to specify which one it is      
> > > >>
> > > >> But why would you have future part with unknown ID?    
> > > > 
> > > > That's what manufacturers do on a very frequent basis.  They tweak something
> > > > that has no affect on the interface or channel scaling etc and release a new part
> > > > with a different ID.  Can be something like a part suited to different operating
> > > > conditions, or with a different supply tolerance.    
> > > 
> > > and it will have a different, known that time ID. How could be "unknown"?  
> > 
> > Known to us, sure, know to old kernel (or other software), not so much.
> > For this sort of driver the main use of fallback compatibles is to work on
> > a not yet aware kernel.  
> 
> So you mean a case that sometime in the future, someone will write a DTS
> with sensirion,slf3s fallback for a sensirion,slf3s-WAHTEVER_NEW_MODEL, use
> old kernel and be surprised it does not work?

To me that is exactly what a fallback compatible is promising - if we have
any kernel / driver that supports the part that we are saying is a valid
fallback then we are saying we support at least the functionality of that
part (sure there may be extra stuff that doesn't work)

We had a long discussion a few years back on whether code that did

	if (read_reg_whoami() != EXPECTED_ID)
		return -ENODEV;

was correct. Someone (maybe Rob?) strongly argued that we must not
do that because it effectively made fallbacks pointless as we always
needed to upgrade the driver. I argued against this (on basis that
swapping in incompatible parts is annoyingly common) but was eventually
persuaded.

If that is not a valid reading of what fallback compatibles mean, is
there any documentation of the rules I can refer to?

> 
> Our goal is not to stop whatever poor code people can ever come up with.
> 
> Every future user wanting to the fallback MUST understand what the
> fallback means.

This is where we disagree.

This is not hard to support, it just means not using generic compatibles
when the device differ (and they are not self describing which these are
not).

Jonathan


> 
> >   
> > >   
> > > >     
> > > >>
> > > >> The device is slf3s with variants. All of known variants have an
> > > >> interface to detect the actual variant. There is no indication that this
> > > >> won't work - why would company remove the ID register?    
> > > > 
> > > > They won't remove the ID, but they will put other values in it to
> > > > indicate new revisions of a part - often entirely backwards compatible
> > > > - sometimes with extra features that we don't use until the driver is updated.
> > > > 
> > > >     
> > > >>
> > > >> But even if this happens, then it would be change of device interface,
> > > >> thus you cannot use generic compatible and you will have a new dedicated
> > > >> compatible.    
> > > > 
> > > > We've had this discussion a number of times for whether an ID register difference
> > > > alone makes a device non compatible, and the answer from DT review has always been
> > > > a firm no and that it is incorrect to reject an unknown ID if the dt-compatible    
> > > 
> > > True, but this is not the case here. That incompatible device would
> > > simply not use this compatible, thus it will not start the probe.  
> > 
> > Taking a rather extreme viewpoint: So fallback compatibles are pointless why do
> > we bother with them?  Note this ID register thing covers most IIO drivers that
> > have fallback compatibles today. This is not a rare corner case.  
> 
> Fallback would not be pointless. It would be used to match and bind the
> driver. Without fallback, driver would not match.
> 
> 
> >   
> > > 
> > >   
> > > > is known.  The compromise that people were happy with was an info print if
> > > > such a mismatch is detected as it might indicate an incompatible part replacement
> > > > and a broken DT.
> > > > 
> > > > Probably 80%+ of IIO bindings with fallback compatibles do not have
> > > > matching "who am I" register values.  This is incredibly common.
> > > >     
> > > >>
> > > >> If the device is actually "slf3s-0600f" (because slf3s is a family),
> > > >> then I am fine with using that as the fallback. Specific front
> > > >> compatibles are also fine in such case.    
> > > > 
> > > > Yes, it's a part in the family. Each of the compatibles here has a
> > > > separate datasheet:
> > > > 
> > > > https://sensirion.com/media/documents/C4F8D965/66F56F53/LQ_DS_SLF3S-0600F_Datasheet.pdf
> > > > https://sensirion.com/media/documents/6971528D/63625D22/Sensirion_Datasheet_SLF3S-1300F.pdf
> > > > etc
> > > > 
> > > > (wonderfully inconsistent file naming ;)
> > > >     
> > > >>
> > > >>
> > > >>    
> > > >>> compatible with.  Given these are providing scaling info that means we
> > > >>> can't realistically support such a future part with a fallback at all.
> > > >>>
> > > >>> That would only be possible if there was feature level discovery. A single
> > > >>> whoami register with no structure to the value is useless for this.      
> > > >>
> > > >> The whoami register defines all the features, no? What would feature
> > > >> discovery improve? ID register is simply logical OR of some feature set,
> > > >> still uniquely identifying the features set/variant.    
> > > > 
> > > > Would be lovely if true. Sadly almost never true. They are typically just
> > > > the next number in a list of parts released. There is no direct information
> > > > on feature set encoded in that value, we have to have a look up table in
> > > > the driver to translate to feature set. (Not relevant here but sometimes
> > > > manufacturers forget to change the number and we get different feature
> > > > sets with the same ID and no discoverability)    
> > > 
> > > I did not mean there is direct information. I meant that you can create
> > > such map, so basically it is defining all features.
> > >   
> > > > 
> > > > If they were an OR of features that would be great.
> > > > 
> > > > The thing is a little structured in this case
> > > > 
> > > > 0x07         Liquid flow sensor
> > > > 0x  03       Product family (e.g. SLF3x)
> > > > 0x    03     Subtype (e.g. SLF3S-0600F)
> > > > 0x      02   Revision number (changes with minor firmware or hardware revisions)
> > > > 
> > > > But both the product family and subtype are numbers to feed into look up tables
> > > > (maybe revision number as well)    
> > > 
> > > The values don't matter. What only matters is that each is unique, you
> > > can map it to a real device (and its datasheet) and features are
> > > decodable from the datasheet.  
> > 
> > Sure, but as above, that breaks the use of fallback compatibles for the incredibly
> > common case of otherwise identical parts that would work fine on an old kernel
> > but where we can't match them because the ID register differs.  
> 
> And it is not a problem. Every other case would not work either. If you
> have specific compatibles - such device would not work. If you have
> fallback with ID mapping to features - such device would not work.
> 
> In all cases device would not work.
> 
> Best regards,
> Krzysztof
> 


^ permalink raw reply

* [PATCH v3 07/10] drm/bridge: synopsys: dw-dp: Add Runtime PM support
From: Sebastian Reichel @ 2026-06-12 18:00 UTC (permalink / raw)
  To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
	Simona Vetter, Dmitry Baryshkov, Luca Ceresoli
  Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
	dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
	linux-arm-kernel, Sebastian Reichel
In-Reply-To: <20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com>

Add runtime PM stubs to the Synopsys DesignWare DisplayPort bridge
driver. Support is not enabled automatically and must be hooked up
in the vendor specific glue code.

Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
 drivers/gpu/drm/bridge/synopsys/dw-dp.c | 27 +++++++++++++++++++++++++++
 include/drm/bridge/dw_dp.h              |  3 +++
 2 files changed, 30 insertions(+)

diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 7fa38145e35c..7f4f36c61484 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1465,6 +1465,8 @@ static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux,
 	if (WARN_ON(msg->size > 16))
 		return -E2BIG;
 
+	ACQUIRE(pm_runtime_active_auto, pm)(dp->dev);
+
 	switch (msg->request & ~DP_AUX_I2C_MOT) {
 	case DP_AUX_NATIVE_WRITE:
 	case DP_AUX_I2C_WRITE:
@@ -1655,6 +1657,8 @@ static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge,
 	struct drm_connector_state *conn_state;
 	int ret;
 
+	pm_runtime_get_sync(dp->dev);
+
 	connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
 	if (!connector) {
 		dev_err(dp->dev, "failed to get connector\n");
@@ -1709,6 +1713,7 @@ static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge,
 	dw_dp_link_disable(dp);
 	bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE);
 	dw_dp_reset(dp);
+	pm_runtime_put_autosuspend(dp->dev);
 }
 
 static bool dw_dp_hpd_detect_link(struct dw_dp *dp, struct drm_connector *connector)
@@ -1729,6 +1734,8 @@ static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge,
 {
 	struct dw_dp *dp = bridge_to_dp(bridge);
 
+	ACQUIRE(pm_runtime_active_auto, pm)(dp->dev);
+
 	if (!dw_dp_hpd_detect(dp))
 		return connector_status_disconnected;
 
@@ -2153,6 +2160,26 @@ void dw_dp_unbind(struct dw_dp *dp)
 }
 EXPORT_SYMBOL_GPL(dw_dp_unbind);
 
+int dw_dp_runtime_suspend(struct dw_dp *dp)
+{
+	clk_disable_unprepare(dp->aux_clk);
+	clk_disable_unprepare(dp->apb_clk);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(dw_dp_runtime_suspend);
+
+int dw_dp_runtime_resume(struct dw_dp *dp)
+{
+	clk_prepare_enable(dp->apb_clk);
+	clk_prepare_enable(dp->aux_clk);
+
+	dw_dp_init_hw(dp);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(dw_dp_runtime_resume);
+
 MODULE_AUTHOR("Andy Yan <andyshrk@163.com>");
 MODULE_DESCRIPTION("DW DP Core Library");
 MODULE_LICENSE("GPL");
diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
index 2127afa26b2c..3037e0290861 100644
--- a/include/drm/bridge/dw_dp.h
+++ b/include/drm/bridge/dw_dp.h
@@ -28,4 +28,7 @@ struct dw_dp_plat_data {
 struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
 			 const struct dw_dp_plat_data *plat_data);
 void dw_dp_unbind(struct dw_dp *dp);
+
+int dw_dp_runtime_suspend(struct dw_dp *dp);
+int dw_dp_runtime_resume(struct dw_dp *dp);
 #endif /* __DW_DP__ */

-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 10/10] drm/bridge: synopsys: dw-dp: Add audio support
From: Sebastian Reichel @ 2026-06-12 18:00 UTC (permalink / raw)
  To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
	Simona Vetter, Dmitry Baryshkov, Luca Ceresoli
  Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
	dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
	linux-arm-kernel, Sebastian Reichel
In-Reply-To: <20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com>

Implement audio support for the Synopsys DesignWare DisplayPort
controller.

Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
 drivers/gpu/drm/bridge/synopsys/dw-dp.c | 221 +++++++++++++++++++++++++++++++-
 1 file changed, 220 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 7f4f36c61484..f1946f2c945d 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -23,17 +23,21 @@
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
 #include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_hdmi_audio_helper.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_of.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_simple_kms_helper.h>
 
+#include <sound/hdmi-codec.h>
+
 #define DW_DP_VERSION_NUMBER			0x0000
 #define DW_DP_VERSION_TYPE			0x0004
 #define DW_DP_ID				0x0008
 
 #define DW_DP_CONFIG_REG1			0x0100
+#define AUDIO_SELECT				GENMASK(2, 1)
 #define DW_DP_CONFIG_REG2			0x0104
 #define DW_DP_CONFIG_REG3			0x0108
 
@@ -110,6 +114,10 @@
 #define HBR_MODE_ENABLE				BIT(10)
 #define AUDIO_DATA_WIDTH			GENMASK(9, 5)
 #define AUDIO_DATA_IN_EN			GENMASK(4, 1)
+#define AUDIO_DATA_IN_EN_CHANNEL12		BIT(0)
+#define AUDIO_DATA_IN_EN_CHANNEL34		BIT(1)
+#define AUDIO_DATA_IN_EN_CHANNEL56		BIT(2)
+#define AUDIO_DATA_IN_EN_CHANNEL78		BIT(3)
 #define AUDIO_INF_SELECT			BIT(0)
 
 #define DW_DP_SDP_VERTICAL_CTRL			0x0500
@@ -253,6 +261,8 @@
 
 #define SDP_REG_BANK_SIZE			16
 
+#define DW_DP_SDP_VERSION			0x12
+
 struct dw_dp_link_caps {
 	bool enhanced_framing;
 	bool tps3_supported;
@@ -305,6 +315,19 @@ struct dw_dp_hotplug {
 	bool long_hpd;
 };
 
+enum dw_dp_audio_interface_support {
+	DW_DP_AUDIO_I2S_ONLY = 0,
+	DW_DP_AUDIO_SPDIF_ONLY = 1,
+	DW_DP_AUDIO_I2S_AND_SPDIF = 2,
+	DW_DP_AUDIO_NONE = 3,
+};
+
+enum dw_dp_audio_interface {
+	DW_DP_AUDIO_I2S = 0,
+	DW_DP_AUDIO_SPDIF = 1,
+	DW_DP_AUDIO_UNUSED,
+};
+
 struct dw_dp {
 	struct drm_bridge bridge;
 	struct device *dev;
@@ -320,6 +343,8 @@ struct dw_dp {
 	int irq;
 	struct work_struct hpd_work;
 	struct dw_dp_hotplug hotplug;
+	enum dw_dp_audio_interface audio_interface;
+	int audio_channels;
 	/* Serialize hpd status access */
 	struct mutex irq_lock;
 
@@ -1837,6 +1862,186 @@ static void dw_dp_bridge_oob_notify(struct drm_bridge *bridge,
 		dev_err_once(dp->dev, "Missing platform handler for OOB HPD handling\n");
 }
 
+static int dw_dp_audio_infoframe_send(struct dw_dp *dp)
+{
+	struct hdmi_audio_infoframe frame;
+	struct dw_dp_sdp sdp;
+	int ret;
+
+	ret = hdmi_audio_infoframe_init(&frame);
+	if (ret < 0)
+		return ret;
+
+	frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+	frame.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
+	frame.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
+	frame.channels = dp->audio_channels;
+
+	ret = hdmi_audio_infoframe_pack_for_dp(&frame, &sdp.base, DW_DP_SDP_VERSION);
+	if (ret < 0)
+		return ret;
+
+	sdp.flags = DW_DP_SDP_VERTICAL_INTERVAL;
+
+	return dw_dp_send_sdp(dp, &sdp);
+}
+
+static int dw_dp_audio_startup(struct drm_bridge *bridge,
+			       struct drm_connector *connector)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+
+	dev_dbg(dp->dev, "audio startup\n");
+	pm_runtime_get_sync(dp->dev);
+
+	return 0;
+}
+
+static void dw_dp_audio_unprepare(struct drm_bridge *bridge,
+				  struct drm_connector *connector)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+
+	/* Disable all audio streams */
+	regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_DATA_IN_EN,
+			   FIELD_PREP(AUDIO_DATA_IN_EN, 0));
+
+	if (dp->audio_interface == DW_DP_AUDIO_SPDIF)
+		clk_disable_unprepare(dp->spdif_clk);
+	else if (dp->audio_interface == DW_DP_AUDIO_I2S)
+		clk_disable_unprepare(dp->i2s_clk);
+
+	dp->audio_interface = DW_DP_AUDIO_UNUSED;
+}
+
+static int dw_dp_audio_prepare(struct drm_bridge *bridge,
+			       struct drm_connector *connector,
+			       struct hdmi_codec_daifmt *daifmt,
+			       struct hdmi_codec_params *params)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+	u8 audio_data_in_en, supported_audio_interfaces;
+	u32 cfg1;
+	int ret;
+
+	/*
+	 * prepare might be called multiple times, so release the clocks
+	 * from previous calls to keep the calls in balance.
+	 */
+	if (dp->audio_interface != DW_DP_AUDIO_UNUSED)
+		dw_dp_audio_unprepare(bridge, connector);
+
+	dp->audio_channels = params->cea.channels;
+	switch (params->cea.channels) {
+	case 1:
+	case 2:
+		audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12;
+		break;
+	case 8:
+		audio_data_in_en = AUDIO_DATA_IN_EN_CHANNEL12 |
+				   AUDIO_DATA_IN_EN_CHANNEL34 |
+				   AUDIO_DATA_IN_EN_CHANNEL56 |
+				   AUDIO_DATA_IN_EN_CHANNEL78;
+		break;
+	default:
+		dev_err(dp->dev, "invalid audio channels %d\n", dp->audio_channels);
+		return -EINVAL;
+	}
+
+	switch (daifmt->fmt) {
+	case HDMI_SPDIF:
+		dp->audio_interface = DW_DP_AUDIO_SPDIF;
+		break;
+	case HDMI_I2S:
+		/*
+		 * It is recommended to use SPDIF instead of I2S, since I2S mode requires
+		 * manually inserting PCUV control bits from userspace and this is done
+		 * automatically in hardware for SPDIF mode.
+		 */
+		dp->audio_interface = DW_DP_AUDIO_I2S;
+		break;
+	default:
+		dev_err(dp->dev, "invalid DAI format %d\n", daifmt->fmt);
+		return -EINVAL;
+	}
+
+	regmap_read(dp->regmap, DW_DP_CONFIG_REG1, &cfg1);
+	supported_audio_interfaces = FIELD_GET(AUDIO_SELECT, cfg1);
+
+	if (supported_audio_interfaces != DW_DP_AUDIO_I2S_AND_SPDIF &&
+	    supported_audio_interfaces != dp->audio_interface) {
+		dev_err(dp->dev, "unsupported DAI %d\n", daifmt->fmt);
+		return -EINVAL;
+	}
+
+	clk_prepare_enable(dp->spdif_clk);
+	clk_prepare_enable(dp->i2s_clk);
+
+	regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1,
+			   AUDIO_DATA_IN_EN | NUM_CHANNELS | AUDIO_DATA_WIDTH |
+			   AUDIO_INF_SELECT | HBR_MODE_ENABLE,
+			   FIELD_PREP(AUDIO_DATA_IN_EN, audio_data_in_en) |
+			   FIELD_PREP(NUM_CHANNELS, dp->audio_channels - 1) |
+			   FIELD_PREP(AUDIO_DATA_WIDTH, params->sample_width) |
+			   FIELD_PREP(AUDIO_INF_SELECT, dp->audio_interface) |
+			   FIELD_PREP(HBR_MODE_ENABLE, 0));
+
+	/* Wait for inf switch */
+	usleep_range(20, 40);
+
+	if (dp->audio_interface == DW_DP_AUDIO_I2S)
+		clk_disable_unprepare(dp->spdif_clk);
+	else if (dp->audio_interface == DW_DP_AUDIO_SPDIF)
+		clk_disable_unprepare(dp->i2s_clk);
+
+	/*
+	 * Send audio stream during vertical and horizontal blanking periods.
+	 * Send out audio timestamp SDP once per video frame during the vertical
+	 * blanking period
+	 */
+	regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL,
+			   EN_AUDIO_STREAM_SDP | EN_AUDIO_TIMESTAMP_SDP,
+			   FIELD_PREP(EN_AUDIO_STREAM_SDP, 1) |
+			   FIELD_PREP(EN_AUDIO_TIMESTAMP_SDP, 1));
+	regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL,
+			   EN_AUDIO_STREAM_SDP,
+			   FIELD_PREP(EN_AUDIO_STREAM_SDP, 1));
+
+	ret = dw_dp_audio_infoframe_send(dp);
+	if (ret < 0)
+		dev_err(dp->dev, "failed to send audio infoframe\n");
+
+	dev_dbg(dp->dev, "audio prepare with %d channels using DAI=%d\n",
+		dp->audio_channels, dp->audio_interface);
+
+	return 0;
+}
+
+static void dw_dp_audio_shutdown(struct drm_bridge *bridge,
+				 struct drm_connector *connector)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+
+	dev_dbg(dp->dev, "audio shutdown\n");
+
+	dw_dp_audio_unprepare(bridge, connector);
+	pm_runtime_put_autosuspend(dp->dev);
+}
+
+static int dw_dp_audio_mute_stream(struct drm_bridge *bridge,
+				   struct drm_connector *connector,
+				   bool enable, int direction)
+{
+	struct dw_dp *dp = bridge_to_dp(bridge);
+
+	dev_dbg(dp->dev, "audio %smute\n", enable ? "" : "un");
+
+	regmap_update_bits(dp->regmap, DW_DP_AUD_CONFIG1, AUDIO_MUTE,
+			   FIELD_PREP(AUDIO_MUTE, enable));
+
+	return 0;
+}
+
 static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
 	.atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
@@ -1850,6 +2055,11 @@ static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
 	.detect = dw_dp_bridge_detect,
 	.edid_read = dw_dp_bridge_edid_read,
 	.oob_notify = dw_dp_bridge_oob_notify,
+
+	.dp_audio_startup = dw_dp_audio_startup,
+	.dp_audio_prepare = dw_dp_audio_prepare,
+	.dp_audio_shutdown = dw_dp_audio_shutdown,
+	.dp_audio_mute_stream = dw_dp_audio_mute_stream,
 };
 
 static int dw_dp_link_retrain(struct dw_dp *dp)
@@ -2076,10 +2286,19 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
 	}
 
 	bridge->of_node = dev->of_node;
-	bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
+	bridge->ops = DRM_BRIDGE_OP_DP_AUDIO |
+		      DRM_BRIDGE_OP_DETECT |
+		      DRM_BRIDGE_OP_EDID |
+		      DRM_BRIDGE_OP_HPD;
 	bridge->type = DRM_MODE_CONNECTOR_DisplayPort;
 	bridge->ycbcr_420_allowed = true;
 
+	dp->audio_interface = DW_DP_AUDIO_UNUSED;
+	bridge->hdmi_audio_dev = dev;
+	bridge->hdmi_audio_max_i2s_playback_channels = 8;
+	bridge->hdmi_audio_dai_port = 1;
+	bridge->hdmi_audio_spdif_playback = true;
+
 	ret = devm_drm_bridge_add(dev, bridge);
 	if (ret)
 		return ERR_PTR(ret);

-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 06/10] drm/rockchip: dw_dp: Implement out-of-band HPD handling
From: Sebastian Reichel @ 2026-06-12 18:00 UTC (permalink / raw)
  To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
	Simona Vetter, Dmitry Baryshkov, Luca Ceresoli
  Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
	dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
	linux-arm-kernel, Sebastian Reichel
In-Reply-To: <20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com>

Implement out-of-band hotplug handling, which will be used to receive
external hotplug information from the USB-C state machine. This is
currently handled by the USBDP PHY, which brings quite some trouble
as the register being accessed requires the power-domain from the DP
controller and also requires custom TypeC HPD info parsing in the
USBDP PHY driver.

In contrast to the USBDP PHY this does not just enable the hotplug
signal when a DP AltMode capable adapter is plugged in, but instead
properly detects if a cable is plugged in for things like USB-C to
HDMI adapters.

Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
 drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 126 ++++++++++++++++++++++++++++--
 1 file changed, 120 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
index 35598ab9fe84..9c53f1d2c29a 100644
--- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
@@ -7,9 +7,12 @@
  */
 
 #include <linux/component.h>
+#include <linux/hw_bitfield.h>
 #include <linux/media-bus-format.h>
+#include <linux/mfd/syscon.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/regmap.h>
 #include <linux/videodev2.h>
 
 #include <drm/bridge/dw_dp.h>
@@ -24,12 +27,54 @@
 
 #include "rockchip_drm_drv.h"
 
+#define ROCKCHIP_MAX_CTRLS 2
+
+#define ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL BIT(10)
+#define ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG BIT(11)
+
+struct rockchip_dw_dp_plat_data {
+	u8 num_ctrls;
+	u32 ctrl_ids[ROCKCHIP_MAX_CTRLS];
+	u32 max_link_rate;
+	u8 pixel_mode;
+	u32 hpd_reg[ROCKCHIP_MAX_CTRLS];
+};
+
 struct rockchip_dw_dp {
 	struct dw_dp *base;
 	struct device *dev;
+	const struct rockchip_dw_dp_plat_data *pdata;
+	struct regmap *vo_grf;
 	struct rockchip_encoder encoder;
+	int id;
+	bool hpd_sel;
+	bool hpd_cfg;
 };
 
+static void dw_dp_rockchip_hpd_sw_sel(void *data, bool force_hpd_from_sw)
+{
+	struct rockchip_dw_dp *dp = data;
+	u32 hpd_reg = dp->pdata->hpd_reg[dp->id];
+
+	dp->hpd_sel = force_hpd_from_sw;
+
+	regmap_write(dp->vo_grf, hpd_reg,
+		     FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, dp->hpd_sel));
+}
+
+static void dw_dp_rockchip_hpd_sw_cfg(void *data, bool hpd)
+{
+	struct rockchip_dw_dp *dp = data;
+	u32 hpd_reg = dp->pdata->hpd_reg[dp->id];
+
+	dev_dbg(dp->dev, "Force HPD connected=%s\n", str_yes_no(hpd));
+
+	dp->hpd_cfg = hpd;
+
+	regmap_write(dp->vo_grf, hpd_reg,
+		     FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, dp->hpd_cfg));
+}
+
 static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder,
 				      struct drm_crtc_state *crtc_state,
 				      struct drm_connector_state *conn_state)
@@ -72,14 +117,49 @@ static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = {
 	.atomic_check		= dw_dp_encoder_atomic_check,
 };
 
+static struct regmap *dp_dp_rockchip_get_vo_grf(struct rockchip_dw_dp *dp)
+{
+	struct device_node *np = dev_of_node(dp->dev);
+	struct of_phandle_args args;
+	struct regmap *regmap;
+	int ret;
+
+	ret = of_parse_phandle_with_args(np, "phys", "#phy-cells", 0, &args);
+	if (ret)
+		return ERR_PTR(-ENODEV);
+
+	/*
+	 * Limit this workaround to RK3576 and RK3588, new platforms should
+	 * add a VO GRF phandle in the DisplayPort DT node.
+	 */
+	if (!of_device_is_compatible(args.np, "rockchip,rk3576-usbdp-phy") &&
+	    !of_device_is_compatible(args.np, "rockchip,rk3588-usbdp-phy")) {
+		regmap = ERR_PTR(-ENODEV);
+		goto out_put_node;
+	}
+
+	regmap = syscon_regmap_lookup_by_phandle(args.np, "rockchip,vo-grf");
+
+out_put_node:
+	of_node_put(args.np);
+	return regmap;
+}
+
 static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data)
 {
-	const struct dw_dp_plat_data *plat_data;
+	const struct rockchip_dw_dp_plat_data *plat_data_const;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dw_dp_plat_data *plat_data;
 	struct drm_device *drm_dev = data;
 	struct rockchip_dw_dp *dp;
 	struct drm_encoder *encoder;
 	struct drm_connector *connector;
-	int ret;
+	struct resource *res;
+	int ret, id;
+
+	plat_data = drmm_kzalloc(drm_dev, sizeof(*plat_data), GFP_KERNEL);
+	if (!plat_data)
+		return -ENOMEM;
 
 	dp = drmm_kzalloc(drm_dev, sizeof(*dp), GFP_KERNEL);
 	if (!dp)
@@ -88,10 +168,38 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *
 	dp->dev = dev;
 	dev_set_drvdata(dev, dp);
 
-	plat_data = of_device_get_match_data(dev);
-	if (!plat_data)
+	plat_data_const = device_get_match_data(dev);
+	if (!plat_data_const)
 		return -ENODEV;
 
+	dp->pdata = plat_data_const;
+
+	res = platform_get_mem_or_io(pdev, 0);
+	if (IS_ERR(res))
+		return PTR_ERR(res);
+
+	/* find the DisplayPort ID from the io address */
+	dp->id = -ENODEV;
+	for (id = 0; id < plat_data_const->num_ctrls; id++) {
+		if (res->start == plat_data_const->ctrl_ids[id]) {
+			dp->id = id;
+			break;
+		}
+	}
+
+	if (dp->id < 0)
+		return dp->id;
+
+	dp->vo_grf = dp_dp_rockchip_get_vo_grf(dp);
+	if (IS_ERR(dp->vo_grf))
+		return PTR_ERR(dp->vo_grf);
+
+	plat_data->max_link_rate = plat_data_const->max_link_rate;
+	plat_data->pixel_mode = plat_data_const->pixel_mode;
+	plat_data->hpd_sw_sel = dw_dp_rockchip_hpd_sw_sel;
+	plat_data->hpd_sw_cfg = dw_dp_rockchip_hpd_sw_cfg;
+	plat_data->data = dp;
+
 	encoder = &dp->encoder.encoder;
 	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node);
 	rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0);
@@ -138,14 +246,20 @@ static void dw_dp_remove(struct platform_device *pdev)
 	component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
 }
 
-static const struct dw_dp_plat_data rk3588_dp_plat_data = {
+static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = {
+	.num_ctrls = 2,
+	.ctrl_ids = {0xfde50000, 0xfde60000},
 	.max_link_rate = 810000,
 	.pixel_mode = DW_DP_MP_QUAD_PIXEL,
+	.hpd_reg = {0x0000, 0x0008},
 };
 
-static const struct dw_dp_plat_data rk3576_dp_plat_data = {
+static const struct rockchip_dw_dp_plat_data rk3576_dp_plat_data = {
+	.num_ctrls = 1,
+	.ctrl_ids = {0x27e40000},
 	.max_link_rate = 810000,
 	.pixel_mode = DW_DP_MP_DUAL_PIXEL,
+	.hpd_reg = {0x0000},
 };
 
 static const struct of_device_id dw_dp_of_match[] = {

-- 
2.53.0


^ permalink raw reply related

* [PATCH RFC v3 09/10] dt-bindings: display: rockchip: dw-dp: fix sound DAI cells
From: Sebastian Reichel @ 2026-06-12 18:00 UTC (permalink / raw)
  To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
	Simona Vetter, Dmitry Baryshkov, Luca Ceresoli
  Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
	dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
	linux-arm-kernel, Sebastian Reichel
In-Reply-To: <20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com>

The RK3588 and RK3576 DesignWare DisplayPort controllers both have two
possible DAI interfaces: I2S and S/PDIF. Thus it is needed to have an
argument to select the right interface.

In case of RK3576 this is not enough though. The RK3576 has the same IP
as RK3588, but configured with Multi Stream Transport (MST) enabled for
up to 3 displays and thus has a total of 6 DAI interfaces (I2S and
S/PDIF for each possible stream. Meanwhile the RK3588 does not support
MST and thus has only 2 DAI interfaces.

The binding update right now only supports the simple single stream
transport (SST) setup. To avoid further DT ABI breakage (or complicated
bindings supporting different number of arguments), it's probably a good
idea to take MST into account now even though the upstream Linux driver
does not yet support it.

I see two options:

1. Adding yet another cell, so that we have the following:
   <&dp_ctrl [display_stream] [i2s_or_spdif]>; potentially append
   extra input ports for MST video data to existing ports node
   (e.g. port@2). I would only handle the sound DAI part in my
   patch and basically use '0' for the display stream and just
   leave the option of using '1' or '2' once MST support is added.

2. The vendor kernel creates a sub-node for each supported display
   stream and puts the ports mapping as well as the DAI reference
   into that. This bundles all information for one display stream
   together, which creates a clean look but the subnode does not
   really describe any real thing in the hardware.

As upstream MST support seems to be quite limited, I wish for some
feedback about the preferred way to handle this.

Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
 .../devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml         | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
index 2b0d9e23e943..1303d0e2145a 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
@@ -83,7 +83,8 @@ properties:
     maxItems: 1
 
   "#sound-dai-cells":
-    const: 0
+    const: 1
+    description: 0 for I2S, 1 for SPDIF
 
 required:
   - compatible
@@ -144,7 +145,7 @@ examples:
         resets = <&cru SRST_DP0>;
         phys = <&usbdp_phy0 PHY_TYPE_DP>;
         power-domains = <&power RK3588_PD_VO0>;
-        #sound-dai-cells = <0>;
+        #sound-dai-cells = <1>;
 
         ports {
           #address-cells = <1>;

-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 08/10] drm/rockchip: dw_dp: Add runtime PM support
From: Sebastian Reichel @ 2026-06-12 18:00 UTC (permalink / raw)
  To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
	Simona Vetter, Dmitry Baryshkov, Luca Ceresoli
  Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
	dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
	linux-arm-kernel, Sebastian Reichel
In-Reply-To: <20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com>

Add support for runtime PM to the Rockchip RK3576/3588 Synopsys
DesignWare DisplayPort driver.

Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
 drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 40 +++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
index 9c53f1d2c29a..1f53228e56d9 100644
--- a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c
@@ -12,6 +12,7 @@
 #include <linux/mfd/syscon.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/regmap.h>
 #include <linux/videodev2.h>
 
@@ -58,6 +59,8 @@ static void dw_dp_rockchip_hpd_sw_sel(void *data, bool force_hpd_from_sw)
 
 	dp->hpd_sel = force_hpd_from_sw;
 
+	ACQUIRE(pm_runtime_active_auto, pm)(dp->dev);
+
 	regmap_write(dp->vo_grf, hpd_reg,
 		     FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, dp->hpd_sel));
 }
@@ -71,6 +74,8 @@ static void dw_dp_rockchip_hpd_sw_cfg(void *data, bool hpd)
 
 	dp->hpd_cfg = hpd;
 
+	ACQUIRE(pm_runtime_active_auto, pm)(dp->dev);
+
 	regmap_write(dp->vo_grf, hpd_reg,
 		     FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, dp->hpd_cfg));
 }
@@ -213,6 +218,12 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *
 	if (IS_ERR(dp->base))
 		return PTR_ERR(dp->base);
 
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_set_autosuspend_delay(dev, 500);
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable runtime PM\n");
+
 	connector = drm_bridge_connector_init(drm_dev, encoder);
 	if (IS_ERR(connector)) {
 		dw_dp_unbind(dp->base);
@@ -246,6 +257,34 @@ static void dw_dp_remove(struct platform_device *pdev)
 	component_del(&pdev->dev, &dw_dp_rockchip_component_ops);
 }
 
+static int dw_dp_rockchip_runtime_suspend(struct device *dev)
+{
+	struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
+
+	return dw_dp_runtime_suspend(dp->base);
+}
+
+static int dw_dp_rockchip_runtime_resume(struct device *dev)
+{
+	struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
+	u32 hpd_reg = dp->pdata->hpd_reg[dp->id];
+	int ret;
+
+	ret = dw_dp_runtime_resume(dp->base);
+	if (ret)
+		return ret;
+
+	regmap_write(dp->vo_grf, hpd_reg,
+		     FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_SEL, dp->hpd_sel) |
+		     FIELD_PREP_WM16(ROCKCHIP_VO_GRF_DP_SINK_HPD_CFG, dp->hpd_cfg));
+
+	return 0;
+}
+
+static const struct dev_pm_ops dw_dp_pm_ops = {
+	RUNTIME_PM_OPS(dw_dp_rockchip_runtime_suspend, dw_dp_rockchip_runtime_resume, NULL)
+};
+
 static const struct rockchip_dw_dp_plat_data rk3588_dp_plat_data = {
 	.num_ctrls = 2,
 	.ctrl_ids = {0xfde50000, 0xfde60000},
@@ -280,5 +319,6 @@ struct platform_driver dw_dp_driver = {
 	.driver = {
 		.name = "dw-dp",
 		.of_match_table = dw_dp_of_match,
+		.pm = pm_ptr(&dw_dp_pm_ops),
 	},
 };

-- 
2.53.0


^ permalink raw reply related

* [PATCH v3 05/10] drm/bridge: synopsys: dw-dp: Support software triggered OOB HPD
From: Sebastian Reichel @ 2026-06-12 18:00 UTC (permalink / raw)
  To: Sandy Huang, Heiko Stübner, Andy Yan, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, David Airlie,
	Simona Vetter, Dmitry Baryshkov, Luca Ceresoli
  Cc: Cristian Ciocaltea, Damon Ding, Dmitry Baryshkov, Alexey Charkov,
	dri-devel, linux-rockchip, linux-kernel, devicetree, kernel,
	linux-arm-kernel, Sebastian Reichel
In-Reply-To: <20260612-synopsys-dw-dp-improvements-v3-0-dc61e6352508@collabora.com>

Add support for USB-C DP AltMode out-of-band hotplug handling. The
handling itself is implemented in the platform specific driver as the
registers to force HPD state are not part of the Designware DisplayPort
IP itself. Instead the platform integration might provide the necessary
functionality to mux the HPD signal.

Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
 drivers/gpu/drm/bridge/synopsys/dw-dp.c | 38 ++++++++++++++++++++++++++++++++-
 include/drm/bridge/dw_dp.h              |  3 +++
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
index 98cff435dfb8..7fa38145e35c 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-dp.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c
@@ -1817,6 +1817,19 @@ static struct drm_bridge_state *dw_dp_bridge_atomic_duplicate_state(struct drm_b
 	return &state->base;
 }
 
+static void dw_dp_bridge_oob_notify(struct drm_bridge *bridge,
+				    struct drm_connector *connector,
+				    enum drm_connector_status status)
+{
+	bool hpd_high = status != connector_status_disconnected;
+	struct dw_dp *dp = bridge_to_dp(bridge);
+
+	if (dp->plat_data.hpd_sw_cfg)
+		dp->plat_data.hpd_sw_cfg(dp->plat_data.data, hpd_high);
+	else
+		dev_err_once(dp->dev, "Missing platform handler for OOB HPD handling\n");
+}
+
 static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
 	.atomic_duplicate_state = dw_dp_bridge_atomic_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
@@ -1829,6 +1842,7 @@ static const struct drm_bridge_funcs dw_dp_bridge_funcs = {
 	.atomic_disable = dw_dp_bridge_atomic_disable,
 	.detect = dw_dp_bridge_detect,
 	.edid_read = dw_dp_bridge_edid_read,
+	.oob_notify = dw_dp_bridge_oob_notify,
 };
 
 static int dw_dp_link_retrain(struct dw_dp *dp)
@@ -1965,6 +1979,19 @@ static void dw_dp_phy_exit(void *data)
 	phy_exit(dp->phy);
 }
 
+static bool dw_dp_is_routed_to_usb_c(struct drm_encoder *encoder)
+{
+	struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL;
+	struct fwnode_handle *fwnode;
+
+	last_bridge = drm_bridge_chain_get_last_bridge(encoder);
+	if (!last_bridge)
+		return false;
+
+	fwnode = of_fwnode_handle(last_bridge->of_node);
+	return fwnode_device_is_compatible(fwnode, "usb-c-connector");
+}
+
 struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
 			 const struct dw_dp_plat_data *plat_data)
 {
@@ -1980,7 +2007,9 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
 
 	dp->dev = dev;
 	dp->pixel_mode = plat_data->pixel_mode;
-
+	dp->plat_data.hpd_sw_sel = plat_data->hpd_sw_sel;
+	dp->plat_data.hpd_sw_cfg = plat_data->hpd_sw_cfg;
+	dp->plat_data.data = plat_data->data;
 	dp->plat_data.max_link_rate = plat_data->max_link_rate;
 	bridge = &dp->bridge;
 	mutex_init(&dp->irq_lock);
@@ -2078,6 +2107,13 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
 		goto unregister_aux;
 	}
 
+	if (dw_dp_is_routed_to_usb_c(encoder)) {
+		dev_dbg(dev, "USB-C mode\n");
+
+		if (dp->plat_data.hpd_sw_sel)
+			dp->plat_data.hpd_sw_sel(dp->plat_data.data, 1);
+	}
+
 	dw_dp_init_hw(dp);
 
 	ret = phy_init(dp->phy);
diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h
index 22105c3e8e4d..2127afa26b2c 100644
--- a/include/drm/bridge/dw_dp.h
+++ b/include/drm/bridge/dw_dp.h
@@ -20,6 +20,9 @@ enum {
 struct dw_dp_plat_data {
 	u32 max_link_rate;
 	u8 pixel_mode;
+	void *data;
+	void (*hpd_sw_sel)(void *data, bool hpd);
+	void (*hpd_sw_cfg)(void *data, bool hpd);
 };
 
 struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,

-- 
2.53.0


^ permalink raw reply related


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