Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH v6 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Sunyun Yang @ 2026-06-25 13:14 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
	Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <3188f63f-5358-48d7-b934-af20a8f95c6f@kernel.org>

Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 20:54写道:
>
> On 08/05/2026 15:40, syyang@lontium.com wrote:
> > +
> > +static void lt9611c_reset(struct lt9611c *lt9611c)
> > +{
> > +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > +     msleep(20);
> > +
> > +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > +     msleep(20);
> > +
> > +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>
> This is just plain wrong. Why do you assert, then de-assert and then
> finally assert AGAIN the reset leaving the device in powerdown stage?
>
I am using software to emulate the hardware RESET button on our EVB.
When the hardware RESET button is pressed while our chip is running,
the signal level changes from HIGH to LOW and then back to HIGH.

Of course, we can also use the following:
static void lt9611c_reset(struct lt9611c *lt9611c)
{
    gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
    msleep(50);
    gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
    msleep(20);
}


> > +     msleep(400);
> > +
> > +     dev_dbg(lt9611c->dev, "lt9611c reset");
>
> Drop such debug statements. This is some downstream, semi-useless code.
> You are not supposed to add debug to every call.
>
it will be fixed in next version.
>
> Best regards,
> Krzysztof

^ permalink raw reply

* Re: [PATCH v6 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Krzysztof Kozlowski @ 2026-06-25 13:17 UTC (permalink / raw)
  To: Sunyun Yang
  Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
	Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <CAFQXuNbKBfyeQL3N3P5QY=6BWoD3O6DSbXN-WMA1rRG9vCs3Kg@mail.gmail.com>

On 25/06/2026 15:14, Sunyun Yang wrote:
> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 20:54写道:
>>
>> On 08/05/2026 15:40, syyang@lontium.com wrote:
>>> +
>>> +static void lt9611c_reset(struct lt9611c *lt9611c)
>>> +{
>>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>> +     msleep(20);
>>> +
>>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
>>> +     msleep(20);
>>> +
>>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>
>> This is just plain wrong. Why do you assert, then de-assert and then
>> finally assert AGAIN the reset leaving the device in powerdown stage?
>>
> I am using software to emulate the hardware RESET button on our EVB.
> When the hardware RESET button is pressed while our chip is running,
> the signal level changes from HIGH to LOW and then back to HIGH.
> 
> Of course, we can also use the following:
> static void lt9611c_reset(struct lt9611c *lt9611c)
> {
>     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
>     msleep(50);
>     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>     msleep(20);
> }

Makes no sense either and you just did not get the point and did not
answer my question. I asked WHY you leave asserted. Answer "we emulate"
is just plain wrong.

So again please answer:

Why do you leave device with reset asserted?

Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH v1 3/3] arm64: dts: qcom: x1e78100-t14s: Add thermal zones for keyboard skin and charging sensors
From: Daniel Lezcano @ 2026-06-25 13:20 UTC (permalink / raw)
  To: Konrad Dybcio, sre, hansg, ilpo.jarvinen, linux, andersson,
	konradybcio, robh, krzk+dt, conor+dt
  Cc: bryan.odonoghue, platform-driver-x86, linux-kernel, linux-hwmon,
	linux-arm-msm, devicetree
In-Reply-To: <ea233160-505d-496d-a321-dc23e7f69d80@oss.qualcomm.com>

On 6/25/26 13:36, Konrad Dybcio wrote:
> On 6/24/26 11:08 PM, Daniel Lezcano wrote:
>> Add thermal zone descriptions for the keyboard skin temperature and
>> battery charging circuitry sensors exposed by the embedded controller.
>>
>> Expose the EC as a thermal sensor provider and define two thermal zones
>> using the temperatures reported by the EC:
>>
>>    - a keyboard skin temperature zone with passive and hot trip points,
>>    - a charging circuitry temperature zone with multiple passive trip
>>      points and CPU frequency mitigation levels.
>>
>> The charging thermal zone progressively throttles the different CPU
>> clusters as the charging circuitry temperature rises and triggers a hot
>> condition at the highest trip point.
>>
>> This provides thermal framework integration for the EC temperature
>> sensors and enables platform thermal management through standard thermal
>> zone definitions.
>>
>> Signed-off-by: Daniel Lezcano <daniel.lezcano@oss.qualcomm.com>
>> ---
> 
> [...]
> 
>> +	ec-charging-thermal {
>> +		polling-delay = <5000>;
>> +		polling-delay-passive = <2000>;
>> +
>> +		thermal-sensors = <&ec 3>;
>> +
>> +		trips {
>> +			psv0: trip-point0 {
> 
> "ec_charging_tripN", please - these labels are file-wide, so it's
> better if they're not overly short
> 
>> +				temperature = <55000>;
>> +				hysteresis = <0>;
>> +				type = "passive";
>> +			};
>> +
>> +			alert0: trip-point1 {
>> +				temperature = <63000>;
>> +				hysteresis = <0>;
>> +				type = "hot";
>> +			};
>> +		};
>> +
>> +		cooling-maps {
>> +			map0 {
>> +				trip = <&psv0>;
>> +				cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu4 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu5 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu6 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu7 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu8 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu9 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
>> +						 <&cpu11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
>> +
>> +			};
> 
> Stray \n above, might also possibly want to throttle the GPU.

At this point, I don't think GPU throttling is needed


^ permalink raw reply

* Re: [PATCH v6 1/2] dt-bindings: bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Sunyun Yang @ 2026-06-25 13:21 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
	Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <206f2e80-132a-4586-bd27-5983783f97ce@kernel.org>

Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 20:59写道:
>
> On 25/06/2026 04:01, Sunyun Yang wrote:
> > Sunyun Yang <syyang@lontium.com> 于2026年6月25日周四 08:52写道:
> >>
> >> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月24日周三 22:05写道:
> >>>
> >>> On 11/05/2026 05:28, Sunyun Yang wrote:
> >>>> <syyang@lontium.com> 于2026年5月8日周五 22:25写道:
> >>>>>
> >>>>> From: Sunyun Yang <syyang@lontium.com>
> >>>>>
> >>>>> LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port
> >>>>> mipi dsi and output hdmi, differences in hardware features:
> >>>>> - LT9611C: supports 1-port mipi dsi to hdmi 1.4
> >>>>> - LT9611EX: supports 2-port mipi dsi to hdmi 1.4
> >>>>> - LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0
> >>>>>
> >>>>> Signed-off-by: Sunyun Yang <syyang@lontium.com>
> >>>>> ---
> >>>>>  .../bindings/display/bridge/lontium,lt9611.yaml           | 8 ++++++--
> >>>>>  1 file changed, 6 insertions(+), 2 deletions(-)
> >>>>>
> >>>>> diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> >>>>> index 429a06057ae8..e0821a63d9d7 100644
> >>>>> --- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> >>>>> +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> >>>>> @@ -4,19 +4,23 @@
> >>>>>  $id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml#
> >>>>>  $schema: http://devicetree.org/meta-schemas/core.yaml#
> >>>>>
> >>>>> -title: Lontium LT9611(UXC) 2 Port MIPI to HDMI Bridge
> >>>>> +title: Lontium LT9611(UXC/C/EX/UXD) 2 Port MIPI DSI to HDMI Bridge
> >>>>>
> >>>>>  maintainers:
> >>>>>    - Vinod Koul <vkoul@kernel.org>
> >>>>>
> >>>>>  description: |
> >>>>> -  The LT9611 and LT9611UXC are bridge devices which convert DSI to HDMI
> >>>>> +  The LT9611、LT9611UXC、LT9611C、LT9611EX and LT9611UXD
> >>>>> +  are bridge devices which convert DSI to HDMI
> >>>>>
> >>>>>  properties:
> >>>>>    compatible:
> >>>>>      enum:
> >>>>>        - lontium,lt9611
> >>>>> +      - lontium,lt9611c
> >>>>> +      - lontium,lt9611ex
> >>>>>        - lontium,lt9611uxc
> >>>>> +      - lontium,lt9611uxd
> >>>>>
> >>>>>    reg:
> >>>>>      maxItems: 1
> >>>>> --
> >>>>
> >>>> Gentle ping.
> >>>> Thanks.
> >>>
> >>> Except mess with threading, your patchset does not build, when applied
> >>> on next-20260618.
> >>>
> >>> What is the base of this?
> >>>
> >>
> >> Thanks for testing my patchset on next-20260618.
> >>
> >> The base of this series is v7.0.
> >>
> > Krzysztof,   Is Sashiko AI review required before merging a patch now?
>
> Fixing obvious bugs in your code is required before merging.
>
> But even without bugs, this cannot be merged - does not apply. And you
> sending the same three different times does not help, because now there
> is complete mess in reviews.
>
We will modify the driver and the device tree, and test it on next-20260623.
Additionally, we would like to ask you a question regarding Sashiko
AI: Is Sashiko AI review required before merging a patch now?

> Best regards,
> Krzysztof

^ permalink raw reply

* Re: [PATCH v6 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Krzysztof Kozlowski @ 2026-06-25 13:24 UTC (permalink / raw)
  To: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard
  Cc: Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, yangsunyun1993, xmzhu, xmzhu, rlyu,
	xbpeng
In-Reply-To: <20260508134009.4582-3-syyang@lontium.com>

On 08/05/2026 15:40, syyang@lontium.com wrote:
> +
> +static int lt9611c_probe(struct i2c_client *client)
> +{
> +	const struct i2c_device_id *id = i2c_client_get_device_id(client);
> +	struct lt9611c *lt9611c;
> +	struct device *dev = &client->dev;
> +	bool fw_updated = false;
> +	int ret;
> +
> +	crc8_populate_msb(lt9611c_crc8_table, LT9611C_CRC_POLYNOMIAL);
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
> +		return dev_err_probe(dev, -ENODEV, "device doesn't support I2C\n");
> +
> +	lt9611c = devm_drm_bridge_alloc(dev, struct lt9611c, bridge, &lt9611c_bridge_funcs);
> +	if (IS_ERR(lt9611c))
> +		return dev_err_probe(dev, PTR_ERR(lt9611c), "drm bridge alloc failed.\n");
> +
> +	lt9611c->dev = dev;
> +	lt9611c->client = client;
> +	lt9611c->chip_type = id->driver_data;

Also, NULL pointer exception here. With simple look I found at least two
easy to reproduce and trigger bugs which makes me questioning how did
you test it.


Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH v6 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Sunyun Yang @ 2026-06-25 13:26 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
	Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <a0352a42-15db-4c7a-ae73-8a4e1543cd50@kernel.org>

Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 21:17写道:
>
> On 25/06/2026 15:14, Sunyun Yang wrote:
> > Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 20:54写道:
> >>
> >> On 08/05/2026 15:40, syyang@lontium.com wrote:
> >>> +
> >>> +static void lt9611c_reset(struct lt9611c *lt9611c)
> >>> +{
> >>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> >>> +     msleep(20);
> >>> +
> >>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> >>> +     msleep(20);
> >>> +
> >>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> >>
> >> This is just plain wrong. Why do you assert, then de-assert and then
> >> finally assert AGAIN the reset leaving the device in powerdown stage?
> >>
> > I am using software to emulate the hardware RESET button on our EVB.
> > When the hardware RESET button is pressed while our chip is running,
> > the signal level changes from HIGH to LOW and then back to HIGH.
> >
> > Of course, we can also use the following:
> > static void lt9611c_reset(struct lt9611c *lt9611c)
> > {
> >     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> >     msleep(50);
> >     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> >     msleep(20);
> > }
>
> Makes no sense either and you just did not get the point and did not
> answer my question. I asked WHY you leave asserted. Answer "we emulate"
> is just plain wrong.
>
> So again please answer:
>
> Why do you leave device with reset asserted?
>

 devicetree:   reset-gpios = <&tlmm 128 GPIO_ACTIVE_HIGH>;

GPIO_ACTIVE_HIGH:

gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);   ------   reset pin
is Low level : Clear the register configuration in the chip to stop
the chip from working.

gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);   ------  reset pin
is high level:  The chip resumes operation.



> Best regards,
> Krzysztof

^ permalink raw reply

* [PATCH v2 0/3] Add Qualcomm JPEG V4L2 encoder for SM8250
From: Atanas Filipov @ 2026-06-25 13:38 UTC (permalink / raw)
  To: linux-media
  Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel, Atanas Filipov

This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder on SM8250.

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/3: dt-bindings: media: qcom,jpeg-encoder: add binding
- 2/3: arm64: dts: qcom: sm8250: add JPEG encoder node
- 3/3: media: qcom: jpeg: add Qualcomm JPEG V4L2 encoder

Supported compatibles:
- qcom,sm8250-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):
- platform: Kona / SM8250 / qcom,sm8250-jenc / RB5
- tested with v4l2-ctl and GStreamer (v4l2jpegenc):
  - single-frame encode: 8192x8192 NV12
  - single-frame encode: 1920x1080 NV12
  - single-frame encode: 1920x1080 GREY
  - GStreamer NV12 pipeline to JPEG files
  - GStreamer GRAY8 pipeline to MJPEG output

Changes since v1:

  Binding (dt-bindings: media: qcom,jpeg-encoder):
  - Removed qcm6490 and sm8550 compatible strings; this series is SM8250
    only.
  - Fixed clock-names block in the binding example.
  - Added operating-points-v2 property documentation.
  - Fixed reg in the example to use the correct 2-cell (64-bit) format
    matching the sm8250 soc node address/size cells.
  - Dropped reg-names: with a single reg entry it is unnecessary.
  - Removed description literal block scalar (|).
  - Dropped _clk suffix from clock-names.
  - Added iommus description in the properties section with
    minItems/maxItems constraints.

  Driver (media: qcom: jpeg):
  - Trimmed hardware register map and definitions.
  - Migrated to devm_clk_bulk_get_all() replacing per-SoC named clock
    arrays.
  - Migrated to the OPP framework for core clock rate and performance
    level management; the OPP table is embedded in the DTS node and maps
    performance levels to frequencies.
  - Switched to devm_platform_ioremap_resource(pdev, 0); reg-names no
    longer needed.
  - Switched to RUNTIME_PM_OPS() and SYSTEM_SLEEP_PM_OPS() macros.
  - Switched to devm_mutex_init().
  - platform_set_drvdata() moved to immediately after allocation.
  - .pm uses pm_ptr().

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

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

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

 .../bindings/media/qcom,jpeg-encoder.yaml     |  135 ++
 arch/arm64/boot/dts/qcom/sm8250.dtsi          |   65 +
 drivers/media/platform/qcom/Kconfig           |    1 +
 drivers/media/platform/qcom/Makefile          |    1 +
 drivers/media/platform/qcom/jpeg/Kconfig      |   17 +
 drivers/media/platform/qcom/jpeg/Makefile     |    9 +
 .../media/platform/qcom/jpeg/qcom_jenc_defs.h |  259 +++
 .../media/platform/qcom/jpeg/qcom_jenc_dev.c  |  313 ++++
 .../media/platform/qcom/jpeg/qcom_jenc_dev.h  |  109 ++
 .../media/platform/qcom/jpeg/qcom_jenc_hdr.c  |  354 ++++
 .../media/platform/qcom/jpeg/qcom_jenc_hdr.h  |  119 ++
 .../media/platform/qcom/jpeg/qcom_jenc_ops.c  | 1510 +++++++++++++++++
 .../media/platform/qcom/jpeg/qcom_jenc_ops.h  |   52 +
 .../media/platform/qcom/jpeg/qcom_jenc_res.c  |   39 +
 .../media/platform/qcom/jpeg/qcom_jenc_res.h  |   30 +
 .../qcom/jpeg/qcom_jenc_v420_hw_info.h        |  527 ++++++
 .../media/platform/qcom/jpeg/qcom_jenc_v4l2.c | 1136 +++++++++++++
 .../media/platform/qcom/jpeg/qcom_jenc_v4l2.h |   25 +
 18 files changed, 4701 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: 8d6dbbbe3ba62de0a63e962ee004afb848c8e3ac
prerequisite-patch-id: 8bfa054e24f9de8d7a09cfab8db0efff173d98fc
-- 
2.34.1


^ permalink raw reply

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

Add device-tree binding for the Qualcomm JPEG encoder hardware block
present in SM8250 (Kona) SoCs.

The JPEG encoder is a standalone hardware IP within the camera subsystem
that performs JPEG compression in memory-to-memory fashion.  It is
separate from the CAMSS ISP pipeline and has its own register space,
interrupt, clocks, power domain, IOMMU streams, and interconnect paths.

Properties documented:
- compatible: qcom,sm8250-jenc
- reg / reg-names: single MMIO region named "jpeg"
- interrupts: single edge-triggered interrupt
- clocks / clock-names: Common clocks and JPEG core clock
- power-domains: TITAN_TOP_GDSC common domain
- iommus: two SMMU stream IDs for JPEG pixel and JPEG DMA processing
- interconnects / interconnect-names
- OPP table mapping performance levels to clock frequencies

Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
---
 .../bindings/media/qcom,jpeg-encoder.yaml     | 135 ++++++++++++++++++
 1 file changed, 135 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..ab8d8951d21f
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
@@ -0,0 +1,135 @@
+# 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:
+  - Atanas Filipov <atanas.filipov@oss.qualcomm.com>
+
+description:
+  Qualcomm JPEG Encoder is the JPEG encode hardware present in Qualcomm SoCs.
+
+properties:
+  compatible:
+    enum:
+      - qcom,sm8250-jenc
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    minItems: 6
+    maxItems: 6
+
+  clock-names:
+    items:
+      - const: bus_hf
+      - const: bus_sf
+      - const: iface
+      - const: cpas
+      - const: axi
+      - const: core
+
+  power-domains:
+    maxItems: 1
+
+  iommus:
+    description:
+      Two SMMU stream IDs for the JPEG hardware. The first entry is for
+      the JPEG core engine; the second is for the JPEG DMA/scale path.
+      The exact stream ID assignment is SoC-specific and not publicly
+      documented by Qualcomm.
+    minItems: 2
+    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
+
+  operating-points-v2: true
+
+  opp-table:
+    type: object
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - interrupts
+  - power-domains
+  - iommus
+  - interconnects
+  - interconnect-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/qcom,camcc-sm8250.h>
+    #include <dt-bindings/clock/qcom,gcc-sm8250.h>
+    #include <dt-bindings/interconnect/qcom,sm8250.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    jpeg-encoder@ac53000 {
+        compatible = "qcom,sm8250-jenc";
+        reg = <0xac53000 0x1000>;
+
+        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 =
+            "bus_hf",
+            "bus_sf",
+            "iface",
+            "cpas",
+            "axi",
+            "core";
+
+        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";
+
+        operating-points-v2 = <&jpeg_opp_table>;
+
+        jpeg_opp_table: opp-table {
+            compatible = "operating-points-v2";
+
+            opp-300000000 {
+                opp-hz = /bits/ 64 <300000000>;
+                opp-level = <0>;
+            };
+
+            opp-600000000 {
+                opp-hz = /bits/ 64 <600000000>;
+                opp-level = <3>;
+            };
+        };
+    };
-- 
2.34.1


^ permalink raw reply related

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

Add the JPEG encoder hardware node to the SM8250 device tree so the
qcom-jpeg V4L2 encoder driver can bind and operate on this platform.

The node wires the resources expected by the qcom,jpeg-encoder binding:
- reg: MMIO region at 0xac53000, size 0x1000
- interrupts: SPI 474, edge-triggered
- power-domains: TITAN_TOP_GDSC (camera top-level power domain)
- clocks: GCC HF/SF AXI clocks and CAM_CC core/AHB/CPAS/CAMNOC clocks
- iommus: two SMMU stream IDs for JPEG pixel and JPEG DMA processing
- interconnects: four ICC paths for AHB config and MNOC data traffic
- operating-points-v2: OPP table with performance levels mapped to JPEG
  clock frequencies

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

diff --git a/arch/arm64/boot/dts/qcom/sm8250.dtsi b/arch/arm64/boot/dts/qcom/sm8250.dtsi
index 7076720413ab..3d741179c916 100644
--- a/arch/arm64/boot/dts/qcom/sm8250.dtsi
+++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi
@@ -4469,6 +4469,71 @@ cci1_i2c1: i2c-bus@1 {
 			};
 		};
 
+		qcom_jpeg_enc: jpeg-encoder@ac53000 {
+			compatible = "qcom,sm8250-jenc";
+
+			reg = <0 0xac53000 0 0x1000>;
+
+			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 = "bus_hf",
+				      "bus_sf",
+				      "iface",
+				      "cpas",
+				      "axi",
+				      "core";
+
+			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";
+
+			operating-points-v2 = <&jpeg_opp_table>;
+
+			jpeg_opp_table: opp-table {
+				compatible = "operating-points-v2";
+
+				opp-300000000 {
+					opp-hz = /bits/ 64 <300000000>;
+					opp-level = <0>;
+					required-opps = <&rpmhpd_opp_svs>;
+				};
+
+				opp-400000000 {
+					opp-hz = /bits/ 64 <400000000>;
+					opp-level = <1>;
+					required-opps = <&rpmhpd_opp_svs>;
+				};
+
+				opp-480000000 {
+					opp-hz = /bits/ 64 <480000000>;
+					opp-level = <2>;
+					required-opps = <&rpmhpd_opp_svs_l1>;
+				};
+
+				opp-600000000-nom {
+					opp-hz = /bits/ 64 <600000000>;
+					opp-level = <3>;
+					required-opps = <&rpmhpd_opp_nom>;
+				};
+			};
+		};
+
 		camss: camss@ac6a000 {
 			compatible = "qcom,sm8250-camss";
 			status = "disabled";
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 3/3] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
From: Atanas Filipov @ 2026-06-25 13:38 UTC (permalink / raw)
  To: linux-media
  Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel, Atanas Filipov
In-Reply-To: <20260625133828.3221781-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.

This series targets SM8250 (Kona) platforms.

The jpeg-encoder node is placed as a top-level SoC node and does not
conflict with the existing CAMSS binding or DTS nodes.

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      |   17 +
 drivers/media/platform/qcom/jpeg/Makefile     |    9 +
 .../media/platform/qcom/jpeg/qcom_jenc_defs.h |  259 +++
 .../media/platform/qcom/jpeg/qcom_jenc_dev.c  |  313 ++++
 .../media/platform/qcom/jpeg/qcom_jenc_dev.h  |  109 ++
 .../media/platform/qcom/jpeg/qcom_jenc_hdr.c  |  354 ++++
 .../media/platform/qcom/jpeg/qcom_jenc_hdr.h  |  119 ++
 .../media/platform/qcom/jpeg/qcom_jenc_ops.c  | 1510 +++++++++++++++++
 .../media/platform/qcom/jpeg/qcom_jenc_ops.h  |   52 +
 .../media/platform/qcom/jpeg/qcom_jenc_res.c  |   39 +
 .../media/platform/qcom/jpeg/qcom_jenc_res.h  |   30 +
 .../qcom/jpeg/qcom_jenc_v420_hw_info.h        |  527 ++++++
 .../media/platform/qcom/jpeg/qcom_jenc_v4l2.c | 1136 +++++++++++++
 .../media/platform/qcom/jpeg/qcom_jenc_v4l2.h |   25 +
 16 files changed, 4501 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..8a96b37b919b
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_QCOM_JENC
+	tristate "Qualcomm V4L2 JPEG Encoder driver"
+	depends on V4L_MEM2MEM_DRIVERS
+	depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST
+	depends on VIDEO_DEV
+	select VIDEOBUF2_DMA_SG
+	select V4L2_JPEG_HELPER
+	select V4L2_MEM2MEM_DEV
+	help
+	  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..7f81caae220b
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_defs.h
@@ -0,0 +1,259 @@
+/* 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
+
+/*
+ * Performance levels for the JPEG core clock, used as OPP level values
+ * (opp-level) in the device tree OPP table.  The numeric values must
+ * match the opp-level entries in the DTS node:
+ *
+ *   LOWSVS  = 0  -> opp-level = <0>  (e.g. 300 MHz on SM8250)
+ *   SVS     = 1  -> opp-level = <1>  (e.g. 400 MHz on SM8250)
+ *   SVS_L1  = 2  -> opp-level = <2>  (e.g. 480 MHz on SM8250)
+ *   NOMINAL = 3  -> opp-level = <3>  (e.g. 600 MHz on SM8250)
+ */
+enum qcom_soc_perf_level {
+	QCOM_SOC_PERF_LOWSVS = 0,
+	QCOM_SOC_PERF_SVS,
+	QCOM_SOC_PERF_SVS_L1,
+	QCOM_SOC_PERF_NOMINAL
+};
+
+/* hardware register field mask identifiers */
+enum qcom_jpeg_mask_id {
+	/* hW version fields */
+	JMSK_HW_VER_STEP,
+	JMSK_HW_VER_MINOR,
+	JMSK_HW_VER_MAJOR,
+
+	/* hW capability fields */
+	JMSK_HW_CAP_ENCODE,
+	JMSK_HW_CAP_DECODE,
+	JMSK_HW_CAP_UPSCALE,
+	JMSK_HW_CAP_DOWNSCALE,
+
+	/* reset command fields for individual JPEG subsystems */
+	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,
+
+	/* hW command fields */
+	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,
+
+	/* core configuration fields */
+	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,
+
+	/* core status fields */
+	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,
+
+	/* interrupt status fields */
+	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,
+
+	/* combined mask for clearing all interrupt status bits */
+	JMSK_IRQ_STATUS_ALL_BITS,
+
+	/* fetch engine (FE) configuration fields */
+	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_FE_VBPAD_CFG,
+
+	/* fetch engine plane address/geometry fields */
+	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_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,
+
+	/* write engine (WE) configuration fields */
+	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,
+
+	/* write engine plane address/geometry fields */
+	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,
+
+	/* encoder configuration fields */
+	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,
+
+	/* scaler configuration fields */
+	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,
+
+	/* dMI table configuration and write fields */
+	JMSK_DMI_CFG,
+	JMSK_DMI_ADDR,
+	JMSK_DMI_DATA,
+
+	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 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 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..626cd6130d9b
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk.h>
+#include <linux/interconnect.h>
+#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.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)
+{
+	int c_idx;
+
+	jenc->num_clks = devm_clk_bulk_get_all(jenc->dev, &jenc->clks);
+	if (jenc->num_clks < 0)
+		return jenc->num_clks;
+
+	for (c_idx = 0; c_idx < jenc->num_clks; c_idx++) {
+		if (!strcmp(jenc->clks[c_idx].id, "core")) {
+			jenc->core_clk = jenc->clks[c_idx].clk;
+			return 0;
+		}
+	}
+
+	return -ENODEV;
+}
+
+static int qcom_jpeg_clk_on(struct qcom_jenc_dev *jenc)
+{
+	struct dev_pm_opp *opp;
+	unsigned long freq;
+	int rc;
+
+	/* look up the OPP entry by level to obtain the core clock frequency */
+	opp = dev_pm_opp_find_level_exact(jenc->dev, jenc->perf);
+	if (IS_ERR(opp)) {
+		dev_err(jenc->dev, "OPP not found for perf level %u: %pe\n", jenc->perf, opp);
+		return PTR_ERR(opp);
+	}
+	freq = dev_pm_opp_get_freq(opp);
+	dev_pm_opp_put(opp);
+
+	rc = clk_set_rate(jenc->core_clk, freq);
+	if (rc) {
+		dev_err(jenc->dev, "core clock set rate failed: %d\n", rc);
+		return rc;
+	}
+
+	rc = clk_bulk_prepare_enable(jenc->num_clks, jenc->clks);
+	if (rc) {
+		clk_set_rate(jenc->core_clk, 0);
+		return rc;
+	}
+
+	return 0;
+}
+
+static void qcom_jpeg_clk_off(struct qcom_jenc_dev *jenc)
+{
+	clk_set_rate(jenc->core_clk, 0);
+	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, "icc_set_bw failed for path %s: %d\n",
+				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 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 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 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 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 = {
+	SYSTEM_SLEEP_PM_OPS(qcom_jpeg_suspend, qcom_jpeg_resume)
+	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;
+	platform_set_drvdata(pdev, jenc);
+	if (devm_mutex_init(&pdev->dev, &jenc->dev_mutex))
+		return -ENOMEM;
+	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(pdev, 0);
+	if (IS_ERR(jenc->jpeg_base))
+		return dev_err_probe(jenc->dev, PTR_ERR(jenc->jpeg_base),
+				     "failed to map JPEG resource\n");
+
+	rc = devm_pm_opp_of_add_table(jenc->dev);
+	if (rc && rc != -ENODEV)
+		return dev_err_probe(jenc->dev, rc, "failed to add OPP table\n");
+
+	rc = qcom_jpeg_clk_init(jenc);
+	if (rc)
+		return dev_err_probe(jenc->dev, rc, "failed to get clocks\n");
+
+	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)
+		return dev_err_probe(jenc->dev, rc, "failed to register V4L2 device\n");
+
+	jenc->perf = QCOM_SOC_PERF_NOMINAL;
+
+	rc = devm_pm_runtime_enable(jenc->dev);
+	if (rc)
+		goto err_v4l2_device_unregister;
+
+	rc = qcom_jpeg_v4l2_register(jenc);
+	if (rc) {
+		dev_err_probe(jenc->dev, rc, "failed to register video device\n");
+		goto err_v4l2_device_unregister;
+	}
+
+	dev_dbg(jenc->dev, "Qualcomm JPEG encoder registered\n");
+
+	return 0;
+
+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
+	},
+	{ }
+};
+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             = pm_ptr(&qcom_jpeg_pm_ops),
+	},
+};
+
+module_platform_driver(qcom_jpeg_platform_driver);
+
+MODULE_DESCRIPTION("QCOM JPEG mem2mem V4L2 encoder");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
new file mode 100644
index 000000000000..0ce1f9751d65
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.h
@@ -0,0 +1,109 @@
+/* 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;
+	struct clk			*core_clk;
+	/* device mutex lock */
+	struct mutex			dev_mutex;
+	atomic_t			ref_count;
+	struct completion		reset_complete;
+	struct completion		stop_complete;
+	/* encoder 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;
+	bool			 hw_acquired;
+
+	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..5af03ec50d68
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_hdr.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/errno.h>
+#include <linux/string.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		0x43
+#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 }
+};
+
+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 }
+};
+
+/* Luminance quantization table */
+static const struct jpeg_dqt_header dqt_luma_data = {
+	.index = 0x00,
+};
+
+/* 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_luma_offs = 0;
+	c->dqt_chroma_offs = 0;
+
+	rc = jb_put_mem(&hdr, &soi_app0, sizeof(soi_app0));
+	if (rc)
+		return rc;
+
+	/* luma DQT is always present */
+	rc = jb_put_mem(&hdr, &dqt_luma_hdr, sizeof(dqt_luma_hdr));
+	if (rc)
+		return rc;
+
+	c->dqt_luma_offs = hdr.pos;
+	rc = jb_put_mem(&hdr, &dqt_luma_data, sizeof(dqt_luma_data));
+	if (rc)
+		return rc;
+
+	/* chroma DQT only for color images */
+	if (fourcc != V4L2_PIX_FMT_GREY) {
+		rc = jb_put_mem(&hdr, &dqt_chroma_hdr, sizeof(dqt_chroma_hdr));
+		if (rc)
+			return rc;
+
+		c->dqt_chroma_offs = hdr.pos;
+		rc = jb_put_mem(&hdr, &dqt_chroma_data, sizeof(dqt_chroma_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_luma_offs) {
+		u32 luma_offs = c->dqt_luma_offs + sizeof(dqt_luma_data.index);
+
+		memcpy(dst + luma_offs, &c->data[luma_offs], sizeof(dqt_luma_data.value));
+	}
+
+	if (c->dqt_chroma_offs) {
+		u32 chroma_offs = c->dqt_chroma_offs + sizeof(dqt_chroma_data.index);
+
+		memcpy(dst + chroma_offs, &c->data[chroma_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..db1e5298e51f
--- /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_luma_offs;
+	u32 dqt_chroma_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..5553ba757194
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
@@ -0,0 +1,1510 @@
+// 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_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 {
+	enum qcom_jpeg_encode_fmt encode;
+	enum qcom_jpeg_memory_fmt memory;
+	u32 fourcc;
+};
+
+/*
+ * 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 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 = 2048, .boundary = JPEG_CFG_MAL_BOUND_2K_BYTES  },
+	{ .bytes = 4096, .boundary = JPEG_CFG_MAL_BOUND_4K_BYTES  }
+};
+
+static const struct qcom_jpeg_scale_blocks jpeg_mcu_blocks[] = {
+	[JPEG_ENCODE_H1V1] = {
+		.w_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+		.h_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+	},
+	[JPEG_ENCODE_H1V2] = {
+		.w_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8  },
+		.h_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+	},
+	[JPEG_ENCODE_H2V1] = {
+		.w_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+		.h_block = { JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8  },
+	},
+	[JPEG_ENCODE_H2V2] = {
+		.w_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+		.h_block = { JPEG_MCU_BLOCK_16, JPEG_MCU_BLOCK_8, JPEG_MCU_BLOCK_8 },
+	},
+	[JPEG_ENCODE_MONO] = {
+		.w_block = { JPEG_MCU_BLOCK_8 },
+		.h_block = { JPEG_MCU_BLOCK_8 }
+	},
+};
+
+static inline int jpeg_get_memory_fmt(u32 fourcc)
+{
+	u32 fi;
+
+	for (fi = 0; fi < ARRAY_SIZE(jpeg_encode_fmt); fi++) {
+		if (jpeg_encode_fmt[fi].fourcc == fourcc)
+			return jpeg_encode_fmt[fi].memory;
+	}
+
+	return -EINVAL;
+}
+
+static inline int jpeg_get_encode_fmt(u32 fourcc)
+{
+	u32 fi;
+
+	for (fi = 0; fi < ARRAY_SIZE(jpeg_encode_fmt); fi++) {
+		if (jpeg_encode_fmt[fi].fourcc == fourcc)
+			return jpeg_encode_fmt[fi].encode;
+	}
+
+	return -EINVAL;
+}
+
+static inline int jpeg_get_mal_boundary(u32 width, const struct qcom_jpeg_mal_boundary *table,
+					u32 count)
+{
+	u32 bi;
+
+	for (bi = 0; bi < count; bi++) {
+		if (table[bi].bytes > width)
+			break;
+	}
+
+	if (!bi)
+		return table[0].boundary;
+
+	if (bi >= count)
+		return table[count - 1].boundary;
+
+	return table[bi - 1].boundary;
+}
+
+static inline int jpeg_get_mcu_geometry(enum qcom_jpeg_encode_fmt fmt, u32 width, u32 height,
+					u32 *blk_w, u32 *blk_h, u32 *mcu_cols, u32 *mcu_rows)
+{
+	const struct qcom_jpeg_scale_blocks *blks;
+	u32 bw = 0, bh = 0;
+	u8 pln;
+
+	blks = &jpeg_mcu_blocks[fmt];
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+		bw = max(bw, blks->w_block[pln]);
+		bh = max(bh, blks->h_block[pln]);
+	}
+
+	if (!bw || !bh)
+		return -EINVAL;
+
+	if (blk_w)
+		*blk_w = bw;
+	if (blk_h)
+		*blk_h = bh;
+
+	if (mcu_cols)
+		*mcu_cols = ALIGN(width, bw) / bw;
+
+	if (mcu_rows)
+		*mcu_rows = ALIGN(height, bh) / bh;
+
+	return 0;
+}
+
+static inline u32 jpeg_io_read(struct qcom_jenc_dev *jenc, u32 offset)
+{
+	return readl(jenc->jpeg_base + offset);
+}
+
+static inline void jpeg_io_write(struct qcom_jenc_dev *jenc, u32 offset, u32 value)
+{
+	writel(value, jenc->jpeg_base + offset);
+}
+
+/*
+ * 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_luma_offs) {
+		base = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_luma_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_chroma_offs) {
+		base = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_chroma_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 u8 *luma_qt = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_luma_offs + 1];
+	const struct qcom_jpeg_reg_offs *offs = ectx->jenc->res->hw_offs;
+	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_chroma_offs) {
+		const u8 *chroma_qt = &ectx->hdr_cache.data[ectx->hdr_cache.dqt_chroma_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, "quality_programmed=%d\n", ectx->quality_programmed);
+}
+
+static void jpeg_sync_sg(struct device *dev,
+			 struct qcom_jpeg_buff *frame,
+			 enum dma_data_direction direction, bool for_device)
+{
+	u8 pln;
+
+	for (pln = 0; pln < QCOM_JPEG_MAX_PLANES; pln++) {
+		struct sg_table *sgt = frame->plns[pln].sgt;
+
+		if (!frame->plns[pln].dma || !sgt)
+			break;
+
+		if (for_device)
+			dma_sync_sgtable_for_device(dev, sgt, direction);
+		else
+			dma_sync_sgtable_for_cpu(dev, sgt, direction);
+	}
+}
+
+static int jpeg_init(struct qcom_jenc_dev *jenc)
+{
+	const struct qcom_jpeg_reg_offs *offs;
+	unsigned long rtime;
+	u32 hw_ver;
+
+	if (WARN_ON_ONCE(!jenc || !jenc->dev || !jenc->jpeg_base || !jenc->res->hw_offs))
+		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");
+		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));
+	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);
+
+	if (!rtime) {
+		dev_err(jenc->dev, "encoder HW stop timeout\n");
+		return -ETIME;
+	}
+
+	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, "FE pln=%d addr=0x%llx idx:%d\n",
+			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;
+
+	for (pln = 0; pln < q->vf.num_planes; pln++) {
+		buff->plns[pln].sgt = vb2_dma_sg_plane_desc(vb2, pln);
+		if (!buff->plns[pln].sgt || !buff->plns[pln].sgt->sgl)
+			return -EINVAL;
+
+		buff->plns[pln].dma = sg_dma_address(buff->plns[pln].sgt->sgl);
+		if (!buff->plns[pln].dma)
+			return -EINVAL;
+
+		buff->plns[pln].size = vb2_plane_size(vb2, pln);
+		if (!buff->plns[pln].size)
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int jpeg_setup_fe_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 *sfmt = &q->vf;
+	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, "FE pln=%d width=%d height=%d stride=%d\n",
+			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;
+	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, "invalid memory format for v4l2 format: %p4cc\n",
+			&sfmt->pixelformat);
+		return -EINVAL;
+	}
+
+	switch (rval) {
+	case JPEG_MEM_FMT_MONO:
+		expected_planes = 1;
+		break;
+	case JPEG_MEM_FMT_PPLANAR:
+		expected_planes = 2;
+		break;
+	case JPEG_MEM_FMT_PLANAR:
+		expected_planes = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (sfmt->num_planes != expected_planes) {
+		dev_err(ectx->dev, "plane mismatch fmt=%u expected=%u got=%u\n",
+			rval, expected_planes, sfmt->num_planes);
+		return -EINVAL;
+	}
+
+	jpeg_rw_bits(jenc, 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);
+
+	/* Scaling is not supported; always configure for 1:1 */
+	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);
+
+	dev_dbg(ectx->dev, "sixteen MCU enabled=%d, %d MCU per blocks\n",
+		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, "failed to get FE mal boundary width=%u\n",
+			sfmt->width);
+		return -EINVAL;
+	}
+	jpeg_rw_bits(jenc, offs->fe_cfg, JMSK_FE_CFG_MAL_BOUNDARY, rval);
+
+	dev_dbg(ectx->dev, "optimal FE mal boundary=%d\n",
+		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, "unsupported encode format %p4cc\n",
+			&sfmt->pixelformat);
+		return -EINVAL;
+	}
+
+	switch (rval) {
+	case JPEG_ENCODE_MONO:
+	case JPEG_ENCODE_H1V1:
+	case JPEG_ENCODE_H2V1:
+		jpeg_rw_bits(jenc, 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, "unsupported encode format: %d\n", rval);
+		return -EINVAL;
+	}
+
+	dev_dbg(ectx->dev, "FE vpad config=%d\n",
+		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;
+	size_t header_size;
+	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);
+
+	header_size = min_t(size_t, vb->planes[0].length, ectx->hdr_cache.size);
+	header_size = qcom_jenc_header_emit(&ectx->hdr_cache, mptr,
+					    (u32)header_size, (u16)q->vf.width,
+					    (u16)q->vf.height);
+
+	dma += header_size;
+	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, "WE pln=%d addr=0x%llx idx:%d\n",
+		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, "WE size=%u\n", 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, "invalid destination width=%d\n", 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, "WE hstep=%u\n",
+		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, "invalid destination height=%d\n", 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, "WE vstep=%u\n",
+		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, "invalid memory format for v4l2 format: %p4cc\n",
+			&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, "failed to get WE mal boundary width=%u\n",
+			dfmt->width);
+		return -EINVAL;
+	}
+	jpeg_rw_bits(jenc, offs->we_cfg, JMSK_WE_CFG_MAL_BOUNDARY, rval);
+
+	dev_dbg(ectx->dev, "optimal WE mal boundary=%d\n",
+		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, "unsupported encode format %p4cc\n",
+			&dfmt->pixelformat);
+		return rval;
+	}
+
+	rval = jpeg_get_mcu_geometry(rval, dfmt->width, dfmt->height, &blk_w, &blk_h,
+				     &mcu_cols, &mcu_rows);
+	if (rval < 0) {
+		dev_err(ectx->dev, "invalid MCU geometry mcu_cols=%d mcu_rows=%d\n",
+			mcu_cols, mcu_rows);
+		return rval;
+	}
+
+	dev_dbg(ectx->dev, "WE blk_w=%u blk_h=%u cols=%u rows=%u\n",
+		blk_w, blk_h, mcu_cols, mcu_rows);
+
+	jpeg_rw_bits(jenc, 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);
+	}
+
+	rval = jpeg_get_encode_fmt(sfmt->pixelformat);
+	if (rval < 0) {
+		dev_err(ectx->dev, "unsupported encode format %p4cc\n",
+			&sfmt->pixelformat);
+		return -EINVAL;
+	}
+
+	rval = jpeg_get_mcu_geometry(rval, dfmt->width, dfmt->height, &blk_w, &blk_h,
+				     &mcu_cols, &mcu_rows);
+	if (rval < 0) {
+		dev_err(ectx->dev, "invalid MCU geometry blk_w=%d blk_h=%d\n",
+			blk_w, blk_h);
+		return -EINVAL;
+	}
+
+	dev_dbg(ectx->dev, "scale blk_w=%u blk_h=%u cols=%u rows=%u\n",
+		blk_w, blk_h, mcu_cols, mcu_rows);
+
+	for (pln = 0; pln < sq->vf.num_planes; pln++) {
+		jpeg_rw_bits(jenc, 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, "scale src=%ux%u dst=%ux%u enable=%d/%d\n",
+		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;
+	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
+	struct qcom_jenc_queue *sq = &ectx->bufq[JENC_SRC_QUEUE];
+	struct v4l2_pix_format_mplane *sfmt = &sq->vf;
+	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, "unsupported encode format %p4cc\n",
+			&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, "invalid MCU geometry mcu_cols=%d mcu_rows=%d\n",
+			mcu_cols, mcu_rows);
+		return -EINVAL;
+	}
+
+	dev_dbg(ectx->dev, "encode blk_w=%u blk_h=%u cols=%u rows=%u\n",
+		blk_w, blk_h, mcu_cols, mcu_rows);
+
+	jpeg_rw_bits(jenc, 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, "encode width=%d height=%d\n",
+		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;
+
+		spin_lock_irqsave(&jenc->hw_lock, flags);
+		ctx = jenc->actx;
+		jenc->actx = NULL;
+		spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+		if (ctx && ctx->bufq[JENC_DST_QUEUE].buff_id >= 0) {
+			struct qcom_jenc_queue *dq = &ctx->bufq[JENC_DST_QUEUE];
+			struct qcom_jpeg_buff *frame = &dq->buff[dq->buff_id];
+			size_t out_size = jpeg_io_read(jenc, offs->enc_out_size);
+
+			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 + ctx->hdr_cache.size);
+		}
+
+		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;
+
+		spin_lock_irqsave(&jenc->hw_lock, flags);
+		ctx = jenc->actx;
+		jenc->actx = NULL;
+		spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+		dev_err(jenc->dev, "encoder hardware failure=0x%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, "cannot set up fetch engine address\n");
+	} else {
+		rc = jpeg_store_we_next(ectx, vb2);
+		if (rc)
+			dev_err(ectx->dev, "cannot set up write engine address\n");
+	}
+
+	return rc;
+}
+
+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_ENCODER_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];
+	u32 quality;
+	u64 load;
+
+	if (!sq->vf.width || !sq->vf.height)
+		return QCOM_SOC_PERF_NOMINAL;
+
+	mutex_lock(&ectx->quality_mutex);
+	quality = clamp(ectx->quality_requested, 1U, 100U);
+	mutex_unlock(&ectx->quality_mutex);
+
+	load = (u64)sq->vf.width * sq->vf.height;
+	load = load * quality;
+	load = div_u64(load, 75);
+
+	if (load <= 2ULL * 1000 * 1000)
+		return QCOM_SOC_PERF_LOWSVS;
+
+	if (load <= 8ULL * 1000 * 1000)
+		return QCOM_SOC_PERF_SVS;
+
+	if (load <= 25ULL * 1000 * 1000)
+		return QCOM_SOC_PERF_SVS_L1;
+
+	return QCOM_SOC_PERF_NOMINAL;
+}
+
+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, "perf=%u src=%ux%u\n", 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_sync(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..175e259886c3
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include "qcom_jenc_ops.h"
+#include "qcom_jenc_res.h"
+
+#include "qcom_jenc_v420_hw_info.h"
+
+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),
+};
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..d7ffc69b142c
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_res.h
@@ -0,0 +1,30 @@
+/* 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"
+
+struct qcom_icc_resource {
+	const char *icc_id;
+	struct {
+		u32 aggr;
+		u32 peak;
+	} pair;
+};
+
+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;
+};
+
+extern const struct qcom_dev_resources qcom_t165_t480_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..bc03b94bd319
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v420_hw_info.h
@@ -0,0 +1,527 @@
+/* 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 \
+	(0x1fffffffu << 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 },
+		.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 },
+		.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..7c0af616fda8
--- /dev/null
+++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
@@ -0,0 +1,1136 @@
+// 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)
+{
+	return container_of(file_to_v4l2_fh(file), struct jenc_context, fh);
+}
+
+static struct qcom_jenc_queue *jpeg_get_bufq(struct jenc_context *ectx, enum qcom_enc_qid id)
+{
+	return &ectx->bufq[id];
+}
+
+static bool jpeg_v4l2_queues_busy(struct jenc_context *ctx)
+{
+	struct vb2_queue *out_q;
+	struct vb2_queue *cap_q;
+
+	out_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+
+	cap_q = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
+	return vb2_is_busy(out_q) || vb2_is_busy(cap_q);
+}
+
+static bool jpeg_is_invalid_src(struct jenc_context *ectx, u32 type)
+{
+	bool is_invalid = (type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
+
+	if (is_invalid)
+		dev_dbg(ectx->dev, "invalid src type or format\n");
+
+	return is_invalid;
+}
+
+static bool jpeg_is_invalid_dst(struct jenc_context *ectx, u32 type)
+{
+	bool is_invalid = (type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
+	if (is_invalid)
+		dev_dbg(ectx->dev, "invalid dst type or format\n");
+
+	return is_invalid;
+}
+
+static const struct jenc_enc_format *jpeg_find_pix_format(enum qcom_enc_qid id, u32 fourcc)
+{
+	const struct jenc_enc_format *efmt;
+	unsigned int i, count;
+
+	if (id == JENC_SRC_QUEUE) {
+		count = JPEG_SRC_FMT_COUNT;
+		efmt = &jpeg_src_formats[0];
+	} else {
+		count = JPEG_DST_FMT_COUNT;
+		efmt = &jpeg_dst_formats[0];
+	}
+
+	for (i = 0; i < count; i++) {
+		if (efmt[i].fourcc == fourcc)
+			return &efmt[i];
+	}
+
+	return NULL;
+}
+
+static const struct jenc_enc_format *jpeg_get_format(struct jenc_context *ectx,
+						     enum qcom_enc_qid qid, u32 pixelformat)
+{
+	const struct jenc_enc_format *efmt = jpeg_find_pix_format(qid, pixelformat);
+
+	if (!efmt)
+		efmt = (qid == JENC_SRC_QUEUE) ? &jpeg_src_formats[0] : &jpeg_dst_formats[0];
+
+	return efmt;
+}
+
+static int jpeg_update_src_planes(const struct jenc_enc_format *ef, struct v4l2_format *v4f)
+{
+	const struct v4l2_format_info *info = v4l2_format_info(ef->fourcc);
+	struct v4l2_pix_format_mplane *f = &v4f->fmt.pix_mp;
+	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;
+	u32 nbx, nby;
+	u64 size;
+
+	f->pixelformat  = ef->fourcc;
+	f->field	= V4L2_FIELD_NONE;
+	f->colorspace	= V4L2_COLORSPACE_SRGB;
+	f->xfer_func	= V4L2_MAP_XFER_FUNC_DEFAULT(f->colorspace);
+	f->ycbcr_enc	= V4L2_MAP_YCBCR_ENC_DEFAULT(f->colorspace);
+	f->quantization =
+		V4L2_MAP_QUANTIZATION_DEFAULT(f->ycbcr_enc == V4L2_YCBCR_ENC_601,
+					      f->colorspace, f->ycbcr_enc);
+
+	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 %p4cc\n",
+		TYPE2QID(f->type), &ef->fourcc);
+
+	if (V4L2_TYPE_IS_CAPTURE(f->type)) {
+		f->fmt.pix_mp.num_planes = 1;
+
+		jpeg_update_dst_plane(ef, f);
+
+		dev_dbg(ectx->dev, "\tImage: %dx%d Size:%9d\n", f->fmt.pix_mp.width,
+			f->fmt.pix_mp.height, f->fmt.pix_mp.plane_fmt[0].sizeimage);
+	} else {
+		/*
+		 * 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)
+{
+	bool was_stopping = ctx->is_stopping;
+	struct vb2_v4l2_buffer *vb;
+
+	ctx->is_stopping = false;
+
+	/*
+	 * Drain the CAPTURE queue.  If a V4L2_ENC_CMD_STOP was in progress
+	 * (was_stopping), signal EOS on the last CAPTURE buffer by calling
+	 * v4l2_m2m_last_buffer_done() with a zero-length payload instead of
+	 * returning it as an error.  All remaining buffers are returned with
+	 * buff_state (DONE or ERROR depending on the call site).
+	 *
+	 * v4l2_m2m_last_dst_buf() returns true when the queue will be empty
+	 * after the current remove, i.e. this is the last buffer.
+	 */
+	while ((vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
+		if (was_stopping && !v4l2_m2m_last_dst_buf(ctx->fh.m2m_ctx)) {
+			vb2_set_plane_payload(&vb->vb2_buf, 0, 0);
+			v4l2_m2m_last_buffer_done(ctx->fh.m2m_ctx, vb);
+		} else {
+			v4l2_m2m_buf_done(vb, buff_state);
+		}
+	}
+
+	while ((vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+		v4l2_m2m_buf_done(vb, buff_state);
+
+	/* Always call job_finish to let m2m schedule the next job. */
+	schedule_work(&ctx->finish_work);
+}
+
+static void jpeg_v4l2_process_cb(void *priv, enum vb2_buffer_state ev, size_t out_size)
+{
+	struct jenc_context *ctx = priv;
+	struct qcom_jenc_dev *jenc = ctx->jenc;
+
+	/* threaded IRQ path */
+	mutex_lock(&jenc->dev_mutex);
+
+	if (ev == VB2_BUF_STATE_DONE && out_size)
+		jpeg_v4l2_work_done(ctx, out_size);
+	else
+		jpeg_v4l2_work_stop(ctx, ev);
+
+	mutex_unlock(&jenc->dev_mutex);
+}
+
+static int jpeg_v4l2_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct jenc_context *ectx = container_of(ctrl->handler, struct jenc_context, ctrl_hdl);
+
+	switch (ctrl->id) {
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		mutex_lock(&ectx->quality_mutex);
+		ectx->quality_requested = ctrl->val;
+		mutex_unlock(&ectx->quality_mutex);
+		break;
+	default:
+		dev_err(ectx->dev, "invalid control id=%#x\n", ctrl->id);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops qcom_jpeg_v4l2_ctrl_ops = {
+	.s_ctrl = jpeg_v4l2_s_ctrl,
+};
+
+static int bop_jpeg_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+				    unsigned int *plns_per_buff, unsigned int sizes[],
+				    struct device *alloc_devs[])
+{
+	struct jenc_context *ectx = vb2_get_drv_priv(vq);
+	struct qcom_jenc_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, "queue=%d size[%d]=%d\n", TYPE2QID(vq->type),
+			pln, sizes[pln]);
+	}
+
+	/*
+	 * Reserve space for the JPEG header that the CPU writes into the
+	 * CAPTURE buffer before the hardware DMA engine appends the bitstream.
+	 * sizeimage covers the worst-case compressed bitstream; JPEG_HEADER_MAX
+	 * is a separate, additional reservation for the header region.
+	 */
+	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");
+		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, "%p4cc %dx%d\n",
+			&sq->vf.pixelformat, sq->vf.width, sq->vf.height);
+
+		for (pln = 0; pln < sq->vf.num_planes; pln++) {
+			dev_dbg(ectx->dev, "\tpln=%d %dx%d bpl:%d size:%d\n", pln,
+				sq->vf.width, sq->vf.height,
+				sq->vf.plane_fmt[pln].bytesperline,
+				sq->vf.plane_fmt[pln].sizeimage);
+		}
+	} else {
+		dev_dbg(ectx->dev, "%p4cc %dx%d\n",
+			&dq->vf.pixelformat,
+			dq->vf.width, dq->vf.height);
+	}
+
+	mutex_lock(&jenc->dev_mutex);
+
+	/*
+	 * Header cache is initialized lazily on CAPTURE QBUF, so valid V4L2
+	 * orders like STREAMON before first QBUF still get a proper JPEG header.
+	 */
+	if (!ectx->hw_acquired) {
+		rc = hw->hw_acquire(ectx, q);
+		if (!rc) {
+			ectx->hw_acquired = true;
+			hw->hw_get_cap(jenc, &hw_caps);
+			dev_dbg(ectx->dev, "hw_caps=0x%x\n", hw_caps);
+		}
+	} else {
+		rc = 0;
+	}
+
+	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);
+
+	if (ectx->hw_acquired) {
+		hw->hw_release(ectx, q);
+		ectx->hw_acquired = false;
+	}
+
+	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;
+	/*
+	 * The JPEG header is written by the CPU into the destination buffer
+	 * before the hardware DMA engine appends the bitstream.  This requires
+	 * a kernel virtual address (vb2_plane_vaddr), which is not guaranteed
+	 * for DMABUF exporters that do not implement vmap.  Restrict CAPTURE
+	 * to memory types that always provide a kernel mapping.
+	 */
+	dq->io_modes		= VB2_MMAP | 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 qcom_jenc_dev *jenc = video_drvdata(file);
+	struct video_device *vdev = video_devdata(file);
+	struct jenc_context *ectx;
+	int rc;
+
+	ectx = kzalloc_obj(*ectx, GFP_KERNEL);
+	if (!ectx)
+		return -ENOMEM;
+
+	ectx->dev  = jenc->dev;
+	ectx->jenc = jenc;
+
+	/* default quality if userspace does not set the control explicitly */
+	ectx->quality_requested = QCOM_JPEG_QUALITY_DEF;
+	ectx->quality_programmed = 0;
+
+	mutex_init(&ectx->ctx_lock);
+	mutex_init(&ectx->quality_mutex);
+
+	INIT_WORK(&ectx->finish_work, jpeg_finish_work);
+	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;
+	struct qcom_jenc_dev *jenc = ectx->jenc;
+	unsigned long flags;
+
+	/*
+	 * Ensure the threaded IRQ handler cannot dereference this context
+	 * after it is freed.  Clear jenc->actx under hw_lock before
+	 * cancelling work and releasing the m2m context.
+	 */
+	spin_lock_irqsave(&jenc->hw_lock, flags);
+	if (jenc->actx == ectx)
+		jenc->actx = NULL;
+	spin_unlock_irqrestore(&jenc->hw_lock, flags);
+
+	cancel_work_sync(&ectx->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..ee69c510faa2
--- /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-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.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

* Re: [PATCH v6 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Sunyun Yang @ 2026-06-25 13:40 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
	Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <CAFQXuNZVE6cZJGwrGKGtWnB-seSJLHFh8zW3jjAs6U4JLZFWng@mail.gmail.com>

Sunyun Yang <syyang@lontium.com> 于2026年6月25日周四 21:26写道:
>
> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 21:17写道:
> >
> > On 25/06/2026 15:14, Sunyun Yang wrote:
> > > Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 20:54写道:
> > >>
> > >> On 08/05/2026 15:40, syyang@lontium.com wrote:
> > >>> +
> > >>> +static void lt9611c_reset(struct lt9611c *lt9611c)
> > >>> +{
> > >>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > >>> +     msleep(20);
> > >>> +
> > >>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > >>> +     msleep(20);
> > >>> +
> > >>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > >>
> > >> This is just plain wrong. Why do you assert, then de-assert and then
> > >> finally assert AGAIN the reset leaving the device in powerdown stage?
> > >>
> > > I am using software to emulate the hardware RESET button on our EVB.
> > > When the hardware RESET button is pressed while our chip is running,
> > > the signal level changes from HIGH to LOW and then back to HIGH.
> > >
> > > Of course, we can also use the following:
> > > static void lt9611c_reset(struct lt9611c *lt9611c)
> > > {
> > >     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > >     msleep(50);
> > >     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > >     msleep(20);
> > > }
> >
> > Makes no sense either and you just did not get the point and did not
> > answer my question. I asked WHY you leave asserted. Answer "we emulate"
> > is just plain wrong.
> >
> > So again please answer:
> >
> > Why do you leave device with reset asserted?
> >
>
>  devicetree:   reset-gpios = <&tlmm 128 GPIO_ACTIVE_HIGH>;
>
> GPIO_ACTIVE_HIGH:
>
> gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);   ------   reset pin
> is Low level : Clear the register configuration in the chip to stop
> the chip from working.
>
> gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);   ------  reset pin
> is high level:  The chip resumes operation.
>
>

Our purpose is: pull the level low to clear the register configuration
in the chip, and then pull it high to allow the MCU inside the chip to
re‑initialize the registers.

>
> > Best regards,
> > Krzysztof

^ permalink raw reply

* Re: [PATCH v1 0/4] This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder.
From: Atanas Filipov @ 2026-06-25 13:43 UTC (permalink / raw)
  To: linux-media
  Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <20260612194417.1737009-1-atanas.filipov@oss.qualcomm.com>

On 6/12/2026 10:44 PM, Atanas Filipov wrote:
> 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

Hi all,

Thank you for the review. v2 has been sent and addresses all comments:

https://lore.kernel.org/linux-media/20260625133828.3221781-1-atanas.filipov@oss.qualcomm.com/T/#t

Regards,
Atanas

^ permalink raw reply

* Re: [PATCH v2 1/3] dt-bindings: media: qcom: Add JPEG encoder binding
From: sashiko-bot @ 2026-06-25 13:46 UTC (permalink / raw)
  To: Atanas Filipov; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260625133828.3221781-2-atanas.filipov@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `clock-names` property is missing from the `required` list despite `clocks` being required and the driver relying on the names.
- [Medium] The commit message claims `reg-names` is documented as a property, but it is missing from the YAML schema.
--

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

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

This commit adds a device-tree binding for the Qualcomm JPEG encoder
hardware block present in SM8250 (Kona) SoCs.

>     Properties documented:
>     - compatible: qcom,sm8250-jenc
>     - reg / reg-names: single MMIO region named "jpeg"
>     - interrupts: single edge-triggered interrupt

[Severity: Medium]
The commit message claims reg-names is documented as a property, but it
is missing from the YAML schema below.

If a device tree uses reg-names = "jpeg" based on this documentation,
the device tree will fail schema validation because additionalProperties: false
is enforced. Should reg-names be added to the properties definition?

> 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..ab8d8951d21fa
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
[ ... ]
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - interrupts

[Severity: High]
Does clock-names need to be included in the required list here?

If an incomplete device tree node provides clocks but omits clock-names,
the schema validation will silently pass since clock-names is not in the
required list.

At runtime, if the driver calls devm_clk_bulk_get_all(), it leaves the clock
id fields as NULL when clock-names is missing. The driver then iterating
over the clocks and calling strcmp(jenc->clks[c_idx].id, "core") would cause
a panic due to a NULL pointer dereference.

> +  - power-domains
> +  - iommus
> +  - interconnects
> +  - interconnect-names

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

^ permalink raw reply

* Re: [PATCH v6 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Krzysztof Kozlowski @ 2026-06-25 13:50 UTC (permalink / raw)
  To: Sunyun Yang
  Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
	Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <CAFQXuNZVE6cZJGwrGKGtWnB-seSJLHFh8zW3jjAs6U4JLZFWng@mail.gmail.com>

On 25/06/2026 15:26, Sunyun Yang wrote:
> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 21:17写道:
>>
>> On 25/06/2026 15:14, Sunyun Yang wrote:
>>> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 20:54写道:
>>>>
>>>> On 08/05/2026 15:40, syyang@lontium.com wrote:
>>>>> +
>>>>> +static void lt9611c_reset(struct lt9611c *lt9611c)
>>>>> +{
>>>>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>>>> +     msleep(20);
>>>>> +
>>>>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
>>>>> +     msleep(20);
>>>>> +
>>>>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>>>
>>>> This is just plain wrong. Why do you assert, then de-assert and then
>>>> finally assert AGAIN the reset leaving the device in powerdown stage?
>>>>
>>> I am using software to emulate the hardware RESET button on our EVB.
>>> When the hardware RESET button is pressed while our chip is running,
>>> the signal level changes from HIGH to LOW and then back to HIGH.
>>>
>>> Of course, we can also use the following:
>>> static void lt9611c_reset(struct lt9611c *lt9611c)
>>> {
>>>     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
>>>     msleep(50);
>>>     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>>     msleep(20);
>>> }
>>
>> Makes no sense either and you just did not get the point and did not
>> answer my question. I asked WHY you leave asserted. Answer "we emulate"
>> is just plain wrong.
>>
>> So again please answer:
>>
>> Why do you leave device with reset asserted?
>>
> 
>  devicetree:   reset-gpios = <&tlmm 128 GPIO_ACTIVE_HIGH>;

That's irrelevant. I do not talk about DT.

Review is happening in specific place - here I question driver code. If
you do not have actual arguments why device is left in reset stage, then
please fix it.


Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH v6 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver
From: Krzysztof Kozlowski @ 2026-06-25 13:51 UTC (permalink / raw)
  To: Sunyun Yang
  Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
	dmitry.baryshkov, maarten.lankhorst, rfoss, mripard,
	Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
	dri-devel, linux-kernel, xmzhu, xmzhu, rlyu, xbpeng
In-Reply-To: <CAFQXuNa8bJCpZBkMs_3mtbK_pjVzDdaDaoGk0KDxiG_Pf7txdg@mail.gmail.com>

On 25/06/2026 15:40, Sunyun Yang wrote:
> Sunyun Yang <syyang@lontium.com> 于2026年6月25日周四 21:26写道:
>>
>> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 21:17写道:
>>>
>>> On 25/06/2026 15:14, Sunyun Yang wrote:
>>>> Krzysztof Kozlowski <krzk@kernel.org> 于2026年6月25日周四 20:54写道:
>>>>>
>>>>> On 08/05/2026 15:40, syyang@lontium.com wrote:
>>>>>> +
>>>>>> +static void lt9611c_reset(struct lt9611c *lt9611c)
>>>>>> +{
>>>>>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>>>>> +     msleep(20);
>>>>>> +
>>>>>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
>>>>>> +     msleep(20);
>>>>>> +
>>>>>> +     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>>>>
>>>>> This is just plain wrong. Why do you assert, then de-assert and then
>>>>> finally assert AGAIN the reset leaving the device in powerdown stage?
>>>>>
>>>> I am using software to emulate the hardware RESET button on our EVB.
>>>> When the hardware RESET button is pressed while our chip is running,
>>>> the signal level changes from HIGH to LOW and then back to HIGH.
>>>>
>>>> Of course, we can also use the following:
>>>> static void lt9611c_reset(struct lt9611c *lt9611c)
>>>> {
>>>>     gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
>>>>     msleep(50);
>>>>     gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>>>     msleep(20);
>>>> }
>>>
>>> Makes no sense either and you just did not get the point and did not
>>> answer my question. I asked WHY you leave asserted. Answer "we emulate"
>>> is just plain wrong.
>>>
>>> So again please answer:
>>>
>>> Why do you leave device with reset asserted?
>>>
>>
>>  devicetree:   reset-gpios = <&tlmm 128 GPIO_ACTIVE_HIGH>;
>>
>> GPIO_ACTIVE_HIGH:
>>
>> gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);   ------   reset pin
>> is Low level : Clear the register configuration in the chip to stop
>> the chip from working.
>>
>> gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);   ------  reset pin
>> is high level:  The chip resumes operation.
>>
>>
> 
> Our purpose is: pull the level low to clear the register configuration
> in the chip, and then pull it high to allow the MCU inside the chip to
> re‑initialize the registers.


And you do completely opposite... so that confirms your code is just wrong.


Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH v19 6/7] coresight: ctcu: enable byte-cntr for TMC ETR devices
From: Jie Gan @ 2026-06-25 13:51 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260625110924.BA8EA1F000E9@smtp.kernel.org>



On 6/25/2026 7:09 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
> - [Critical] Global function pointer accessed locklessly leads to Use-After-Free on driver unload
> - [Critical] Global sysfs ops pointer triggers NULL pointer dereference on ETR without CTCU
> - [High] Driver remove/unbind blocks indefinitely waiting for userspace file closure
> - [High] ctcu_byte_cntr_stop() skips setting enable=false for SYSFS mode, causing infinite read hangs
> - [High] TOCTOU race re-enables ETR hardware after framework disables it
> - [High] Missing wake_up() in error path deadlocks driver remove()
> - [High] Unsynchronized checks of irq_enabled lead to resource leaks and driver unbind hangs
> - [High] Data race on concurrent read() calls causes NULL pointer dereference
> - [Medium] Spurious Interrupt Handling due to missing hardware status check in shared IRQ
> - [Medium] Error codes (-ERESTARTSYS) from byte_cntr_ops are swallowed and convert to EOF
> - [Medium] Masked error code breaks sysfs reading fallback on memory failure
> --
> 
> commit 128ffef0065b7b8a8c73e5e3f9b0ce8bebea0593
> Author: Jie Gan <jie.gan@oss.qualcomm.com>
> 
> coresight: ctcu: enable byte-cntr for TMC ETR devices
> 
> This patch introduces the byte-cntr function provided by the CTCU device to
> transfer data from the ETR buffer to userspace. It sets up an interrupt
> handler to track data size thresholds and switches buffers when full or on
> timeout. It also integrates with the TMC ETR framework via sysfs ops.
> 
>> diff --git a/drivers/hwtracing/coresight/coresight-ctcu-byte-cntr.c b/drivers/hwtracing/coresight/coresight-ctcu-byte-cntr.c
>> new file mode 100644
>> index 0000000000000..5ab97a71f02f6
>> --- /dev/null
>> +++ b/drivers/hwtracing/coresight/coresight-ctcu-byte-cntr.c
> [ ... ]
>> +static irqreturn_t byte_cntr_handler(int irq, void *data)
>> +{
>> +	struct ctcu_byte_cntr *byte_cntr_data = data;
>> +
>> +	atomic_inc(&byte_cntr_data->irq_cnt);
>> +	wake_up(&byte_cntr_data->wq);
>> +
>> +	return IRQ_HANDLED;
>> +}
> 
> [Severity: Medium]
> Is this handler missing a hardware status check?
> 
> Since the IRQ is registered with IRQF_SHARED, another device sharing the line
> could fire an interrupt. By unconditionally returning IRQ_HANDLED without
> verifying the interrupt source, the kernel's spurious interrupt detection
> might be suppressed, and the CTCU buffer switch logic could be triggered
> prematurely.

Although the driver requests the interrupt with IRQF_SHARED, the 
underlying hardware/DT topology provides a dedicated IRQ line on 
supported platforms. So, no other interrupt sources are present, and the 
handler can safely treat all interrupts as CTCU byte-cntr events.

> 
> [ ... ]
>> +static struct ctcu_byte_cntr *ctcu_get_byte_cntr(struct coresight_device *ctcu,
>> +						 struct coresight_device *etr)
>> +{
>> +	struct ctcu_drvdata *drvdata = dev_get_drvdata(ctcu->dev.parent);
> 
> [Severity: Critical]
> Could this dereference a NULL pointer if an ETR without a connected CTCU
> is used?
> 
> If the system has multiple ETRs and one lacks a CTCU, the globally registered
> byte_cntr_sysfs_ops might pass a NULL ctcu pointer here. This would cause a
> panic when ctcu->dev.parent is accessed.

This scenario assumes an invalid hardware topology. Byte-cntr 
functionality is only applicable when an ETR is connected to a CTCU via 
the CoreSight graph. tmc_etr_get_ctcu_device() discovers the CTCU using 
this hardware/DT linkage. In the absence of such a connection, no 
byte-byte path exists. Supported platforms guarantee this connection for 
byte-cntr capable ETRs; therefore, the reported NULL-CTCU case is 
outside valid configurations.

> 
> [ ... ]
>> +static bool ctcu_byte_cntr_switch_buffer(struct tmc_drvdata *etr_drvdata,
>> +					 struct ctcu_byte_cntr *byte_cntr_data)
>> +{
> [ ... ]
>> +	raw_spin_unlock_irqrestore(&etr_drvdata->spinlock, flags);
>> +
>> +	/* Restart the ETR once a free buffer is available */
>> +	if (found_free_buf &&
>> +	    coresight_get_mode(etr_drvdata->csdev) != CS_MODE_DISABLED)
>> +		tmc_etr_enable_disable_hw(etr_drvdata, true);
>> +
>> +	return found_free_buf;
>> +}
> 
> [Severity: High]
> Does this lockless check introduce a race condition?
> 
> If a concurrent operation disables the trace session and drops the lock before
> setting the mode to CS_MODE_DISABLED, this code might incorrectly re-enable
> the hardware after the framework has explicitly disabled it.
> 

Disabling the trace session implies that the ETR is already disabled in 
the byte-cntr read flow, in which case the switch-buffer function cannot 
be entered.
The only possible scenario is that ETR becomes disabled in the middle of 
a buffer switch operation. However, the code explicitly checks the 
CoreSight mode (CS_MODE) before re-enabling ETR, ensuring that it is 
only re-enabled when appropriate.

The byte-cntr read path drains the ping-pong buffers regardless of 
whether the ETR is enabled or disabled.

> [ ... ]
>> +static ssize_t tmc_byte_cntr_get_data(struct tmc_drvdata *etr_drvdata, loff_t pos,
>> +				      size_t len, char **bufpp)
>> +{
> [ ... ]
>> +	pos = byte_cntr_data->buf_node->pos;
>> +	sysfs_buf = byte_cntr_data->buf_node->sysfs_buf;
>> +	actual = tmc_etr_read_sysfs_buf(sysfs_buf, pos, len, bufpp);
>> +	if (actual <= 0) {
>> +		/* Reset buf_node upon reading is finished or failed */
>> +		byte_cntr_data->buf_node->reading = false;
>> +		byte_cntr_data->buf_node = NULL;
> 
> [Severity: High]
> Can concurrent read operations cause a NULL pointer dereference here?
> 
> If two threads read from the same ETR file descriptor, they could concurrently
> access and modify byte_cntr_data->buf_node without serialization, causing one
> thread to clear the pointer while the other is still using it.
> 

Byte-cntr supports a single active read session. This is enforced via 
byte_cntr_data->reading in tmc_read_prepare_byte_cntr(), with concurrent 
open attempts returning -EBUSY. The buf_node is exclusively owned by 
this session and cleared during unprepare. The reported race assumes 
unsupported concurrent consumption of the same session.

> [ ... ]
>> +static int tmc_read_prepare_byte_cntr(struct tmc_drvdata *etr_drvdata)
>> +{
>> +	struct coresight_device *ctcu = tmc_etr_get_ctcu_device(etr_drvdata);
>> +	struct ctcu_byte_cntr *byte_cntr_data;
>> +	unsigned long flags;
>> +	int ret = 0;
>> +
>> +	/* byte-cntr is operating with SYSFS mode being enabled only */
>> +	if (coresight_get_mode(etr_drvdata->csdev) != CS_MODE_SYSFS)
>> +		return -EINVAL;
>> +
>> +	byte_cntr_data = ctcu_get_byte_cntr(ctcu, etr_drvdata->csdev);
>> +	if (!byte_cntr_data || !byte_cntr_data->irq_enabled)
>> +		return -EINVAL;
>> +
>> +	raw_spin_lock_irqsave(&byte_cntr_data->spin_lock, flags);
> 
> [Severity: High]
> Does checking irq_enabled outside the spinlock create a race?
> 
> A concurrent sysfs write could set irq_enabled to 0 immediately before the
> lock is acquired. If reading is then set to true, read_unprepare() will later
> abort early upon seeing irq_enabled is 0. This would leak the buffer list and
> leave reading permanently true, causing the driver unbind to hang forever.
> 

irq_enabled serves as a configuration gate prior to starting byte-cntr 
and is not a runtime control for active sessions. Once prepare succeeds, 
operation is governed by enable, reading, hardware registers, 
waitqueue/timeout, and buffer state. Sysfs prevents modification after 
reading is set, and toggling irq_enabled during an active session is not 
a supported use case.

> [ ... ]
>> +	byte_cntr_data->reading = true;
>> +	raw_spin_unlock_irqrestore(&byte_cntr_data->spin_lock, flags);
>> +	/* Setup an available etr_buf_list for byte-cntr */
>> +	ret = tmc_create_etr_buf_list(etr_drvdata, 2);
>> +	if (ret) {
>> +		byte_cntr_data->reading = false;
>> +		return ret;
>> +	}
> 
> [Severity: High]
> Is this error path missing a wake_up() call?
> 
> If this allocation fails, the reading flag is cleared, but concurrent readers
> waiting in the driver's remove function won't be notified, potentially
> deadlocking the unbind process.
> 

No wakeup is required in this case. Allocation failure occurs before 
establishing any byte-cntr read session. The prepare function returns 
failure to TMC open, meaning no reader exists. wake_up() is only 
necessary for active sessions that need to be terminated or completed, 
not for failed prepare paths.

> [ ... ]
>> +void ctcu_byte_cntr_stop(struct coresight_device *csdev, struct coresight_path *path)
>> +{
>> +	struct coresight_device *sink = coresight_get_sink(path);
>> +	struct ctcu_byte_cntr *byte_cntr_data;
>> +
>> +	if (coresight_get_mode(sink) == CS_MODE_SYSFS)
>> +		return;
> 
> [Severity: High]
> Should the enable flag be cleared for SYSFS mode here?
> 
> By returning early, enable remains true. Readers waiting in
> tmc_byte_cntr_get_data() might never receive the EOF condition when the
> trace is stopped, causing them to loop indefinitely.
> 

The early return is intentional. In SYSFS mode, ETR remains enabled and 
byte-cntr may actively serve the read path. Forcing enable=false in this 
state would prematurely terminate the read session and disrupt trace 
transfer. The enable=false transition is reserved for teardown paths 
(e.g., CTCU removal or non-SYSFS disable).

>> diff --git a/drivers/hwtracing/coresight/coresight-ctcu-core.c b/drivers/hwtracing/coresight/coresight-ctcu-core.c
>> index e8720026c9e3f..2da1a6f3d29f4 100644
>> --- a/drivers/hwtracing/coresight/coresight-ctcu-core.c
>> +++ b/drivers/hwtracing/coresight/coresight-ctcu-core.c
> [ ... ]
>> +static void ctcu_platform_remove(struct platform_device *pdev)
>> +{
>> +	struct ctcu_drvdata *drvdata = platform_get_drvdata(pdev);
>> +	struct ctcu_byte_cntr *byte_cntr_data;
>> +	unsigned long flags;
>> +	int i;
>> +
>> +	if (WARN_ON(!drvdata))
>> +		return;
>> +
>> +	/*
>> +	 * Signal all active byte-cntr readers to exit, then wait for them to
>> +	 * finish before resetting the ops pointer and freeing driver data.
>> +	 * Without this, a reader blocked in wait_event_interruptible_timeout()
>> +	 * would access the freed ctcu_drvdata wait-queue head (use-after-free).
>> +	 */
>> +	for (i = 0; i < ETR_MAX_NUM; i++) {
>> +		byte_cntr_data = &drvdata->byte_cntr_data[i];
>> +		raw_spin_lock_irqsave(&byte_cntr_data->spin_lock, flags);
>> +		/* Set enable=false for all ports to signal teardown to racing readers */
>> +		byte_cntr_data->enable = false;
>> +		if (!byte_cntr_data->reading) {
>> +			raw_spin_unlock_irqrestore(&byte_cntr_data->spin_lock, flags);
>> +			continue;
>> +		}
>> +		raw_spin_unlock_irqrestore(&byte_cntr_data->spin_lock, flags);
>> +		wake_up_all(&byte_cntr_data->wq);
>> +		wait_event(byte_cntr_data->wq, !byte_cntr_data->reading);
>> +	}
> 
> [Severity: High]
> Could this wait indefinitely during driver remove?
> 
> The reading flag is only cleared when the userspace file is closed. A
> userspace process leaving the file descriptor open could permanently block
> module unload or device unbind.

This behavior is expected and not a driver defect. During removal, 
ctcu_platform_remove() sets enable=false and wakes byte-cntr readers, 
releasing blocked reads from the wait queue. Any remaining wait 
corresponds to userspace retaining open TMC file descriptors. The kernel 
cannot forcibly close user file descriptors without violating lifetime 
rules; thus, waiting ensures that CTCU state is not freed while still 
potentially referenced.

> 
>> diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
>> index 2b26ce6455a7b..e78f8891f11e0 100644
>> --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
>> +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> [ ... ]
>> +ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
>> +				loff_t pos, size_t len, char **bufpp)
>> +{
>> +	ssize_t ret;
>> +	const struct tmc_sysfs_ops *byte_cntr_ops = READ_ONCE(byte_cntr_sysfs_ops);
>> +
>> +	if (byte_cntr_ops) {
>> +		ret = byte_cntr_ops->get_trace_data(drvdata, pos, len, bufpp);
>> +		/* Return the filled buffer */
>> +		if (ret > 0 || ret == -ENOMEM)
>> +			return ret;
>> +	}
>> +
>> +	return tmc_etr_read_sysfs_buf(drvdata->sysfs_buf, pos, len, bufpp);
>> +}
> 
> [Severity: Medium]
> Does this silently swallow -ERESTARTSYS?
> 
> If a signal interrupts the wait inside tmc_byte_cntr_get_data(), it returns
> -ERESTARTSYS, which falls through here to a normal sysfs buffer read. This
> might convert the signal into an EOF or return unsynced data instead of
> letting userspace handle the signal cleanly.
> 

This is intentional. Byte-cntr operates as an optimized read path 
layered over the existing TMC sysfs read. If byte-cntr does not yield 
valid data, the implementation falls back to the generic TMC path. As 
such, propagating -ERESTARTSYS to userspace is unnecessary; it simply 
results in bypassing byte-cntr for that read attempt.

> [ ... ]
>> +int tmc_read_prepare_etr(struct tmc_drvdata *drvdata)
>> +{
>> +	int ret = 0;
>> +	unsigned long flags;
>> +	const struct tmc_sysfs_ops *byte_cntr_ops;
>>   
>>   	/* config types are set a boot time and never change */
>>   	if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR))
>>   		return -EINVAL;
>>   
>> +	byte_cntr_ops = READ_ONCE(byte_cntr_sysfs_ops);
>> +	if (byte_cntr_ops) {
>> +		ret = byte_cntr_ops->read_prepare(drvdata);
> 
> [Severity: Critical]
> Is it safe to access this global function pointer locklessly?
> 
> If the trace file is opened and the CTCU module is concurrently unloaded, the
> memory backing byte_cntr_ops could be freed before read_prepare() executes,
> leading to a use-after-free regression.
> 

The reported use-after-free is not substantiated. byte_cntr_sysfs_ops is 
registered during CTCU probe and reset during CTCU remove. Existing 
byte-cntr readers are first signaled with enable=false, then woken and 
fully drained before the CTCU state is freed. After unregister, new 
callers observe NULL ops and fall back to the standard TMC read path. As 
such, this concern conflates global ops registration with an actual 
post-remove data/session UAF, which does not occur.

>> +		if (!ret || ret == -EBUSY)
>> +			return ret;
>> +
>> +		ret = 0;
>> +	}
> 
> [Severity: Medium]
> Why are errors like -ENOMEM from read_prepare() masked here?
> 
> If the memory allocation fails inside tmc_read_prepare_byte_cntr(), hiding the
> error breaks the fallback sysfs reading path, leading to delays and hanging
> operations instead of propagating the failure properly.

This is an intential fallback design. Byte-cntr is an optional 
enhancement to the generic TMC ETR sysfs read path. When prepare fails 
for reasons other than an active session (-EBUSY), the driver falls back 
to the generic read-prepare path. Masking non--EBUSY errors is therefore 
intentional and not indicative of a defect.

Thanks,
Jie

> 


^ permalink raw reply

* Re: [PATCH v2 2/3] arm64: dts: qcom: sm8250: Add JPEG encoder node
From: sashiko-bot @ 2026-06-25 13:52 UTC (permalink / raw)
  To: Atanas Filipov; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260625133828.3221781-3-atanas.filipov@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] The OPP node name `opp-600000000-nom` violates the standard `operating-points-v2` device tree binding schema.
- [Low] The `qcom_jpeg_enc` node is missing the `status = "disabled";` property, enabling it unconditionally across all SM8250 boards.
--

commit 00630d0f1aab28af7917b8a2e9b6daedb6cc070c
Author: Atanas Filipov <atanas.filipov@oss.qualcomm.com>

arm64: dts: qcom: sm8250: Add JPEG encoder node
    
Add the JPEG encoder hardware node to the SM8250 device tree so the
qcom-jpeg V4L2 encoder driver can bind and operate on this platform.

> diff --git a/arch/arm64/boot/dts/qcom/sm8250.dtsi b/arch/arm64/boot/dts/qcom/sm8250.dtsi
> index 7076720413ab2..3d741179c9165 100644
> --- a/arch/arm64/boot/dts/qcom/sm8250.dtsi
> +++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi
> @@ -4469,6 +4469,71 @@ cci1_i2c1: i2c-bus@1 {
>  			};
>  		};
>  
> +		qcom_jpeg_enc: jpeg-encoder@ac53000 {
> +			compatible = "qcom,sm8250-jenc";
> +
> +			reg = <0 0xac53000 0 0x1000>;

[Severity: Low]
Should this node include a status = "disabled" property?

Without it, the JPEG encoder driver will unconditionally probe on all SM8250
boards that include sm8250.dtsi, registering a /dev/video node and allocating
memory even on boards that might not want this IP enabled. Other multimedia
blocks in this file typically default to disabled and are enabled per-board.

[ ... ]

> +			jpeg_opp_table: opp-table {
> +				compatible = "operating-points-v2";
> +
> +				opp-300000000 {
> +					opp-hz = /bits/ 64 <300000000>;
> +					opp-level = <0>;
> +					required-opps = <&rpmhpd_opp_svs>;
> +				};

[ ... ]

> +				opp-600000000-nom {
> +					opp-hz = /bits/ 64 <600000000>;
> +					opp-level = <3>;
> +					required-opps = <&rpmhpd_opp_nom>;
> +				};
> +			};
> +		};

[Severity: Low]
Does the node name opp-600000000-nom violate the operating-points-v2 device
tree binding schema?

Running dtbs_check will likely flag this or silently skip validating the
node's properties because the name does not match the enforced regular
expression for opp nodes.

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

^ permalink raw reply

* Re: [PATCH v4 1/3] perf: marvell: Add MPAM partid filtering to CN10K TAD PMU
From: Ben Horgan @ 2026-06-25 13:53 UTC (permalink / raw)
  To: Geetha sowjanya, linux-perf-users, linux-kernel, linux-arm-kernel,
	devicetree
  Cc: mark.rutland, will, krzk+dt, james.morse
In-Reply-To: <20260618153610.13649-2-gakula@marvell.com>

Hi Geetha,

+CC James

On 6/18/26 16:36, Geetha sowjanya wrote:
> From: Tanmay Jagdale <tanmay@marvell.com>
> 
> The TAD PMU exposes counters that can be filtered by MPAM partition id
> for a subset of allocation and hit events.
> 
> Add a 9-bit partid format attribute (config1) and route counter programming
> through variant-specific ops so CN10K keeps MPAM-capable programming while
> Odyssey keeps the reduced event set without advertising partid in sysfs.
> 
> Probe no longer mutates the platform_device MMIO resource (walk a local
> map_start), rejects tad-cnt / page sizes of zero, validates the memory
> window against tad-cnt, and registers the perf PMU before hotplug with
> correct unwind.
> 
> Example:
>   perf stat -e tad/tad_alloc_any,partid=0x12,partid_en=1/ -- <program>

Where is the user expected to get the PARTID from? The MPAM driver
considers the PARTID as an internal only value.

resctrl does support a 'debug' mount option which will show the CLOSID
associated with a control group. Whilst the CLOSID is often the PARTID,
it is really a set of PARTIDs. When the cdp mount option is used, CLOSID
maps to 2 PARTIDs and if we use PARTID narrowing to give us more
monitors, as in proposed in [1], then the set of PARTIDs may be bigger.
Furthermore, if the PARTID narrowing scheme is made dynamic the size of
the PARTID set may change when control or monitoring groups are created
or deleted.

It seems that a way to map from a resctrl control group to the set of
PARTIDs is required and a mechanism to tie this to lifetime of the
resctrl mount.

Perhaps some helpers along the lines of:

int resctrl_mount_generation(void)
int mpam_rdtgrp_to_partid_is_static(int mount_gen)
int resctrl_rdtgrp_generation(char *name)
int mpam_rdtgrp_to_partid_count(char *name, int rdt_gen)
int mpam_rdtgrp_to_partid_array(char *name, int rdt_gen, int* partids)

The rdtgrp generation is to an attempt to avoid having to use a debug
interface in anger and cope with renaming of control groups in resctrl.
This does seem a bit unwieldly so hopefully there is better way to do this.

Sorry to throw a spanner in the works.

Thanks,

Ben

> 
> Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
> Signed-off-by: Geetha sowjanya <gakula@marvell.com>
> ---
> 
> Changelog (since v3)
> --------------------
> - Restore cpuhp_state_add_instance_nocalls before perf_pmu_register in probe
>   so users cannot attach events before the hotplug instance exists; unwind
>   removes the hotplug instance if perf registration fails.
> - Add perf_ready: tad_pmu_offline_cpu skips perf_pmu_migrate_context until after
>   successful perf_pmu_register, so a CPU offline between hotplug add and perf
>   register does not touch perf core state for an unregistered PMU.
> 
> Changelog (since v2)
> --------------------
> - Validate the eventId using an appropriate mask to ensure
>   it is restricted to 8 bits.
> 
> Changelog (since v1)
> --------------------
> - Fix config1 filter enable to use bit 9 consistently with the PMU format
>   string (partid_en) and reject reserved bits with GENMASK(9, 0).
> - Register perf_pmu_register before cpuhp_state_add_instance_nocalls and
>   unregister on hotplug failure.
> 
>  drivers/perf/marvell_cn10k_tad_pmu.c | 220 +++++++++++++++++++++------
>  1 file changed, 171 insertions(+), 49 deletions(-)
> 
> diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c
> index 51ccb0befa05..340be3776fe7 100644
> --- a/drivers/perf/marvell_cn10k_tad_pmu.c
> +++ b/drivers/perf/marvell_cn10k_tad_pmu.c
> @@ -7,6 +7,8 @@
>  #define pr_fmt(fmt) "tad_pmu: " fmt
>  
>  #include <linux/io.h>
> +#include <linux/bits.h>
> +#include <linux/compiler.h>
>  #include <linux/module.h>
>  #include <linux/of.h>
>  #include <linux/cpuhotplug.h>
> @@ -14,12 +16,20 @@
>  #include <linux/platform_device.h>
>  #include <linux/acpi.h>
>  
> -#define TAD_PFC_OFFSET		0x800
> -#define TAD_PFC(counter)	(TAD_PFC_OFFSET | (counter << 3))
>  #define TAD_PRF_OFFSET		0x900
> -#define TAD_PRF(counter)	(TAD_PRF_OFFSET | (counter << 3))
> +#define TAD_PFC_OFFSET		0x800
> +#define TAD_PFC(base, counter)	((base) | ((u64)(counter) << 3))
> +#define TAD_PRF(base, counter)	((base) | ((u64)(counter) << 3))
>  #define TAD_PRF_CNTSEL_MASK	0xFF
> +#define TAD_PRF_MATCH_PARTID	BIT(8)
> +#define TAD_PRF_PARTID_NS	BIT(10)
> +/*
> + * config1: bits 0..8 MPAM partition id (including 0); bit 9 requests
> + * filtering for MPAM-capable events. All-zero config1 means no filter.
> + */
> +#define TAD_PARTID_FILTER_EN	BIT(9)
>  #define TAD_MAX_COUNTERS	8
> +#define TAD_EVENT_SEL_MASK	GENMASK(7, 0)
>  
>  #define to_tad_pmu(p) (container_of(p, struct tad_pmu, pmu))
>  
> @@ -27,30 +37,94 @@ struct tad_region {
>  	void __iomem	*base;
>  };
>  
> +enum mrvl_tad_pmu_version {
> +	TAD_PMU_V1 = 1,
> +	TAD_PMU_V2,
> +};
> +
> +struct tad_pmu_data {
> +	int id;
> +	u64 tad_prf_offset;
> +	u64 tad_pfc_offset;
> +};
> +
>  struct tad_pmu {
>  	struct pmu pmu;
>  	struct tad_region *regions;
>  	u32 region_cnt;
>  	unsigned int cpu;
> +	/* Set after successful perf_pmu_register(); gates offline migration. */
> +	bool perf_ready;
> +	const struct tad_pmu_ops *ops;
> +	const struct tad_pmu_data *pdata;
>  	struct hlist_node node;
>  	struct perf_event *events[TAD_MAX_COUNTERS];
>  	DECLARE_BITMAP(counters_map, TAD_MAX_COUNTERS);
>  };
>  
> -enum mrvl_tad_pmu_version {
> -	TAD_PMU_V1 = 1,
> -	TAD_PMU_V2,
> -};
> -
> -struct tad_pmu_data {
> -	int id;
> +struct tad_pmu_ops {
> +	void (*start_counter)(struct tad_pmu *pmu, struct perf_event *event);
>  };
>  
>  static int tad_pmu_cpuhp_state;
>  
> +static void tad_pmu_start_counter(struct tad_pmu *pmu,
> +				  struct perf_event *event)
> +{
> +	const struct tad_pmu_data *pdata = pmu->pdata;
> +	struct hw_perf_event *hwc = &event->hw;
> +	u32 event_idx = (u32)(event->attr.config & TAD_EVENT_SEL_MASK);
> +	u32 counter_idx = hwc->idx;
> +	u64 partid_filter = 0;
> +	u64 reg_val;
> +	u64 cfg1 = event->attr.config1;
> +	bool use_mpam = cfg1 & TAD_PARTID_FILTER_EN;
> +	u32 partid = (u32)(cfg1 & GENMASK(8, 0));
> +	int i;
> +
> +	for (i = 0; i < pmu->region_cnt; i++)
> +		writeq_relaxed(0, pmu->regions[i].base +
> +			       TAD_PFC(pdata->tad_pfc_offset, counter_idx));
> +
> +	if (use_mpam && event_idx > 0x19 && event_idx < 0x21) {
> +		partid_filter = TAD_PRF_MATCH_PARTID | TAD_PRF_PARTID_NS |
> +				((u64)partid << 11);
> +	}
> +
> +
> +	for (i = 0; i < pmu->region_cnt; i++) {
> +		reg_val = event_idx & 0xFF;
> +		reg_val |= partid_filter;
> +		writeq_relaxed(reg_val, pmu->regions[i].base +
> +			       TAD_PRF(pdata->tad_prf_offset, counter_idx));
> +	}
> +}
> +
> +static void tad_pmu_v2_start_counter(struct tad_pmu *pmu,
> +				     struct perf_event *event)
> +{
> +	const struct tad_pmu_data *pdata = pmu->pdata;
> +	struct hw_perf_event *hwc = &event->hw;
> +	u32 event_idx = (u32)(event->attr.config & TAD_EVENT_SEL_MASK);
> +	u32 counter_idx = hwc->idx;
> +	u64 reg_val;
> +	int i;
> +
> +	for (i = 0; i < pmu->region_cnt; i++)
> +		writeq_relaxed(0, pmu->regions[i].base +
> +			       TAD_PFC(pdata->tad_pfc_offset, counter_idx));
> +
> +	for (i = 0; i < pmu->region_cnt; i++) {
> +		reg_val = event_idx & 0xFF;
> +		writeq_relaxed(reg_val, pmu->regions[i].base +
> +			       TAD_PRF(pdata->tad_prf_offset, counter_idx));
> +	}
> +}
> +
>  static void tad_pmu_event_counter_read(struct perf_event *event)
>  {
>  	struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
> +	const struct tad_pmu_data *pdata = tad_pmu->pdata;
>  	struct hw_perf_event *hwc = &event->hw;
>  	u32 counter_idx = hwc->idx;
>  	u64 prev, new;
> @@ -60,7 +134,7 @@ static void tad_pmu_event_counter_read(struct perf_event *event)
>  		prev = local64_read(&hwc->prev_count);
>  		for (i = 0, new = 0; i < tad_pmu->region_cnt; i++)
>  			new += readq(tad_pmu->regions[i].base +
> -				     TAD_PFC(counter_idx));
> +				     TAD_PFC(pdata->tad_pfc_offset, counter_idx));
>  	} while (local64_cmpxchg(&hwc->prev_count, prev, new) != prev);
>  
>  	local64_add(new - prev, &event->count);
> @@ -69,16 +143,14 @@ static void tad_pmu_event_counter_read(struct perf_event *event)
>  static void tad_pmu_event_counter_stop(struct perf_event *event, int flags)
>  {
>  	struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
> +	const struct tad_pmu_data *pdata = tad_pmu->pdata;
>  	struct hw_perf_event *hwc = &event->hw;
>  	u32 counter_idx = hwc->idx;
>  	int i;
>  
> -	/* TAD()_PFC() stop counting on the write
> -	 * which sets TAD()_PRF()[CNTSEL] == 0
> -	 */
>  	for (i = 0; i < tad_pmu->region_cnt; i++) {
>  		writeq_relaxed(0, tad_pmu->regions[i].base +
> -			       TAD_PRF(counter_idx));
> +			       TAD_PRF(pdata->tad_prf_offset, counter_idx));
>  	}
>  
>  	tad_pmu_event_counter_read(event);
> @@ -89,26 +161,10 @@ static void tad_pmu_event_counter_start(struct perf_event *event, int flags)
>  {
>  	struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
>  	struct hw_perf_event *hwc = &event->hw;
> -	u32 event_idx = event->attr.config;
> -	u32 counter_idx = hwc->idx;
> -	u64 reg_val;
> -	int i;
>  
>  	hwc->state = 0;
>  
> -	/* Typically TAD_PFC() are zeroed to start counting */
> -	for (i = 0; i < tad_pmu->region_cnt; i++)
> -		writeq_relaxed(0, tad_pmu->regions[i].base +
> -			       TAD_PFC(counter_idx));
> -
> -	/* TAD()_PFC() start counting on the write
> -	 * which sets TAD()_PRF()[CNTSEL] != 0
> -	 */
> -	for (i = 0; i < tad_pmu->region_cnt; i++) {
> -		reg_val = event_idx & 0xFF;
> -		writeq_relaxed(reg_val,	tad_pmu->regions[i].base +
> -			       TAD_PRF(counter_idx));
> -	}
> +	tad_pmu->ops->start_counter(tad_pmu, event);
>  }
>  
>  static void tad_pmu_event_counter_del(struct perf_event *event, int flags)
> @@ -128,7 +184,6 @@ static int tad_pmu_event_counter_add(struct perf_event *event, int flags)
>  	struct hw_perf_event *hwc = &event->hw;
>  	int idx;
>  
> -	/* Get a free counter for this event */
>  	idx = find_first_zero_bit(tad_pmu->counters_map, TAD_MAX_COUNTERS);
>  	if (idx == TAD_MAX_COUNTERS)
>  		return -EAGAIN;
> @@ -148,6 +203,9 @@ static int tad_pmu_event_counter_add(struct perf_event *event, int flags)
>  static int tad_pmu_event_init(struct perf_event *event)
>  {
>  	struct tad_pmu *tad_pmu = to_tad_pmu(event->pmu);
> +	const struct tad_pmu_data *pdata = tad_pmu->pdata;
> +	u32 event_idx = (u32)(event->attr.config & TAD_EVENT_SEL_MASK);
> +	u64 cfg1 = event->attr.config1;
>  
>  	if (event->attr.type != event->pmu->type)
>  		return -ENOENT;
> @@ -158,6 +216,23 @@ static int tad_pmu_event_init(struct perf_event *event)
>  	if (event->state != PERF_EVENT_STATE_OFF)
>  		return -EINVAL;
>  
> +	if (event->attr.config & ~TAD_EVENT_SEL_MASK)
> +		return -EINVAL;
> +
> +	if (pdata->id == TAD_PMU_V2) {
> +		if (cfg1)
> +			return -EINVAL;
> +	} else {
> +		if ((cfg1 & GENMASK(8, 0)) && !(cfg1 & TAD_PARTID_FILTER_EN))
> +			return -EINVAL;
> +		if (cfg1 & TAD_PARTID_FILTER_EN) {
> +			if (event_idx <= 0x19 || event_idx >= 0x21)
> +				return -EINVAL;
> +		}
> +		if (cfg1 & ~GENMASK(9, 0))
> +			return -EINVAL;
> +	}
> +
>  	event->cpu = tad_pmu->cpu;
>  	event->hw.idx = -1;
>  	event->hw.config_base = event->attr.config;
> @@ -232,7 +307,7 @@ static struct attribute *ody_tad_pmu_event_attrs[] = {
>  	TAD_PMU_EVENT_ATTR(tad_hit_ltg, 0x1e),
>  	TAD_PMU_EVENT_ATTR(tad_hit_any, 0x1f),
>  	TAD_PMU_EVENT_ATTR(tad_tag_rd, 0x20),
> -	TAD_PMU_EVENT_ATTR(tad_tot_cycle, 0xFF),
> +	TAD_PMU_EVENT_ATTR(tad_tot_cycle, 0xff),
>  	NULL
>  };
>  
> @@ -242,9 +317,13 @@ static const struct attribute_group ody_tad_pmu_events_attr_group = {
>  };
>  
>  PMU_FORMAT_ATTR(event, "config:0-7");
> +PMU_FORMAT_ATTR(partid, "config1:0-8");
> +PMU_FORMAT_ATTR(partid_en, "config1:9-9");
>  
>  static struct attribute *tad_pmu_format_attrs[] = {
>  	&format_attr_event.attr,
> +	&format_attr_partid.attr,
> +	&format_attr_partid_en.attr,
>  	NULL
>  };
>  
> @@ -253,6 +332,16 @@ static struct attribute_group tad_pmu_format_attr_group = {
>  	.attrs = tad_pmu_format_attrs,
>  };
>  
> +static struct attribute *ody_tad_pmu_format_attrs[] = {
> +	&format_attr_event.attr,
> +	NULL
> +};
> +
> +static struct attribute_group ody_tad_pmu_format_attr_group = {
> +	.name = "format",
> +	.attrs = ody_tad_pmu_format_attrs,
> +};
> +
>  static ssize_t tad_pmu_cpumask_show(struct device *dev,
>  				struct device_attribute *attr, char *buf)
>  {
> @@ -281,16 +370,25 @@ static const struct attribute_group *tad_pmu_attr_groups[] = {
>  
>  static const struct attribute_group *ody_tad_pmu_attr_groups[] = {
>  	&ody_tad_pmu_events_attr_group,
> -	&tad_pmu_format_attr_group,
> +	&ody_tad_pmu_format_attr_group,
>  	&tad_pmu_cpumask_attr_group,
>  	NULL
>  };
>  
> +static const struct tad_pmu_ops tad_pmu_ops = {
> +	.start_counter = tad_pmu_start_counter,
> +};
> +
> +static const struct tad_pmu_ops tad_pmu_v2_ops = {
> +	.start_counter = tad_pmu_v2_start_counter,
> +};
> +
>  static int tad_pmu_probe(struct platform_device *pdev)
>  {
>  	const struct tad_pmu_data *dev_data;
>  	struct device *dev = &pdev->dev;
>  	struct tad_region *regions;
> +	resource_size_t map_start;
>  	struct tad_pmu *tad_pmu;
>  	struct resource *res;
>  	u32 tad_pmu_page_size;
> @@ -298,7 +396,6 @@ static int tad_pmu_probe(struct platform_device *pdev)
>  	u32 tad_cnt;
>  	int version;
>  	int i, ret;
> -	char *name;
>  
>  	tad_pmu = devm_kzalloc(&pdev->dev, sizeof(*tad_pmu), GFP_KERNEL);
>  	if (!tad_pmu)
> @@ -312,6 +409,7 @@ static int tad_pmu_probe(struct platform_device *pdev)
>  		return -ENODEV;
>  	}
>  	version = dev_data->id;
> +	tad_pmu->pdata = dev_data;
>  
>  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>  	if (!res) {
> @@ -338,22 +436,31 @@ static int tad_pmu_probe(struct platform_device *pdev)
>  		dev_err(&pdev->dev, "Can't find tad-cnt property\n");
>  		return ret;
>  	}
> +	if (!tad_cnt || !tad_page_size || !tad_pmu_page_size) {
> +		dev_err(&pdev->dev, "Invalid tad-cnt or page size\n");
> +		return -EINVAL;
> +	}
>  
>  	regions = devm_kcalloc(&pdev->dev, tad_cnt,
>  			       sizeof(*regions), GFP_KERNEL);
>  	if (!regions)
>  		return -ENOMEM;
>  
> -	/* ioremap the distributed TAD pmu regions */
> -	for (i = 0; i < tad_cnt && res->start < res->end; i++) {
> -		regions[i].base = devm_ioremap(&pdev->dev,
> -					       res->start,
> +	map_start = res->start;
> +	for (i = 0; i < tad_cnt; i++) {
> +		if (map_start > res->end ||
> +		    tad_pmu_page_size > (resource_size_t)(res->end - map_start + 1)) {
> +			dev_err(&pdev->dev, "TAD PMU mem window too small for tad-cnt=%u\n",
> +				tad_cnt);
> +			return -EINVAL;
> +		}
> +		regions[i].base = devm_ioremap(&pdev->dev, map_start,
>  					       tad_pmu_page_size);
>  		if (!regions[i].base) {
>  			dev_err(&pdev->dev, "TAD%d ioremap fail\n", i);
>  			return -ENOMEM;
>  		}
> -		res->start += tad_page_size;
> +		map_start += tad_page_size;
>  	}
>  
>  	tad_pmu->regions = regions;
> @@ -374,14 +481,16 @@ static int tad_pmu_probe(struct platform_device *pdev)
>  		.read		= tad_pmu_event_counter_read,
>  	};
>  
> -	if (version == TAD_PMU_V1)
> +	if (version == TAD_PMU_V1) {
>  		tad_pmu->pmu.attr_groups = tad_pmu_attr_groups;
> -	else
> +		tad_pmu->ops		 = &tad_pmu_ops;
> +	} else {
>  		tad_pmu->pmu.attr_groups = ody_tad_pmu_attr_groups;
> +		tad_pmu->ops		 = &tad_pmu_v2_ops;
> +	}
>  
>  	tad_pmu->cpu = raw_smp_processor_id();
>  
> -	/* Register pmu instance for cpu hotplug */
>  	ret = cpuhp_state_add_instance_nocalls(tad_pmu_cpuhp_state,
>  					       &tad_pmu->node);
>  	if (ret) {
> @@ -389,19 +498,24 @@ static int tad_pmu_probe(struct platform_device *pdev)
>  		return ret;
>  	}
>  
> -	name = "tad";
> -	ret = perf_pmu_register(&tad_pmu->pmu, name, -1);
> -	if (ret)
> +	ret = perf_pmu_register(&tad_pmu->pmu, "tad", -1);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Error %d registering perf PMU\n", ret);
>  		cpuhp_state_remove_instance_nocalls(tad_pmu_cpuhp_state,
>  						    &tad_pmu->node);
> +		return ret;
> +	}
>  
> -	return ret;
> +	WRITE_ONCE(tad_pmu->perf_ready, true);
> +
> +	return 0;
>  }
>  
>  static void tad_pmu_remove(struct platform_device *pdev)
>  {
>  	struct tad_pmu *pmu = platform_get_drvdata(pdev);
>  
> +	WRITE_ONCE(pmu->perf_ready, false);
>  	cpuhp_state_remove_instance_nocalls(tad_pmu_cpuhp_state,
>  						&pmu->node);
>  	perf_pmu_unregister(&pmu->pmu);
> @@ -410,12 +524,17 @@ static void tad_pmu_remove(struct platform_device *pdev)
>  #if defined(CONFIG_OF) || defined(CONFIG_ACPI)
>  static const struct tad_pmu_data tad_pmu_data = {
>  	.id   = TAD_PMU_V1,
> +	.tad_prf_offset = TAD_PRF_OFFSET,
> +	.tad_pfc_offset = TAD_PFC_OFFSET,
>  };
> +
>  #endif
>  
>  #ifdef CONFIG_ACPI
>  static const struct tad_pmu_data tad_pmu_v2_data = {
>  	.id   = TAD_PMU_V2,
> +	.tad_prf_offset = TAD_PRF_OFFSET,
> +	.tad_pfc_offset = TAD_PFC_OFFSET,
>  };
>  #endif
>  
> @@ -451,6 +570,9 @@ static int tad_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
>  	struct tad_pmu *pmu = hlist_entry_safe(node, struct tad_pmu, node);
>  	unsigned int target;
>  
> +	if (!READ_ONCE(pmu->perf_ready))
> +		return 0;
> +
>  	if (cpu != pmu->cpu)
>  		return 0;
>  
> @@ -491,6 +613,6 @@ static void __exit tad_pmu_exit(void)
>  module_init(tad_pmu_init);
>  module_exit(tad_pmu_exit);
>  
> -MODULE_DESCRIPTION("Marvell CN10K LLC-TAD Perf driver");
> +MODULE_DESCRIPTION("Marvell CN10K LLC-TAD perf driver");
>  MODULE_AUTHOR("Bhaskara Budiredla <bbudiredla@marvell.com>");
>  MODULE_LICENSE("GPL v2");


^ permalink raw reply

* Re: [PATCH v1 0/4] This series adds support for the Qualcomm JPEG V4L2 mem2mem encoder.
From: Krzysztof Kozlowski @ 2026-06-25 13:53 UTC (permalink / raw)
  To: Atanas Filipov, linux-media
  Cc: mchehab, bod, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <364a79bf-e424-4237-ab13-18e38299ecec@oss.qualcomm.com>

On 25/06/2026 15:43, Atanas Filipov wrote:
> On 6/12/2026 10:44 PM, Atanas Filipov wrote:
>>   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
> 
> Hi all,
> 
> Thank you for the review. v2 has been sent and addresses all comments:
> 
> https://lore.kernel.org/linux-media/20260625133828.3221781-1-atanas.filipov@oss.qualcomm.com/T/#t
> 

Problem is that is not true. You did not address all comments. You did
not reply to them and you did not implement them. I just checked FIRST
comment and stopped there.

Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH v2 3/3] media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder
From: sashiko-bot @ 2026-06-25 13:54 UTC (permalink / raw)
  To: Atanas Filipov; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260625133828.3221781-4-atanas.filipov@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
- [High] Premature buffer return before stopping hardware DMA causes memory corruption.
- [High] Hardware programmed with single scatterlist element despite `VIDEOBUF2_DMA_SG` configuration.
- [High] Use-After-Free of `qcom_jenc_dev` due to `devm_kzalloc` allocation.
- [High] Acquiring a sleeping mutex while holding a spinlock in `job_abort` causes deadlock.
- [High] Use-After-Free of encoder context (`ectx`) between the threaded IRQ handler and file release.
- [High] Incorrect synchronization order schedules `finish_work` after `cancel_work_sync`, causing Use-After-Free.
- [High] Deadlock between the threaded IRQ handler and `stop_streaming` leads to unclocked hardware access.
- [High] DMA buffer overflow because `VIDIOC_CREATE_BUFS` bypasses header size padding.
- [Medium] Inverted logic for V4L2_BUF_FLAG_LAST marks the wrong buffers as EOS and drops pending frames.
- [Medium] Race condition caused by `reinit_completion` occurring after a hardware command.
- [Medium] Per-context tracking of DMI tables causes lost hardware updates in interleaved streams.
- [Medium] Concurrent read-modify-write on the `int_mask` hardware register causes lost updates.
--

commit 28b8fe8e7d711888f653c3ed94bf36f5bc5799ac
Author: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
Subject: media: qcom: jpeg: Add Qualcomm JPEG V4L2 encoder

This commit adds a new Qualcomm JPEG encoder driver implemented on top of the
V4L2 mem2mem framework. It handles vb2 queue handling, format negotiation,
JPEG header handling, interrupt-driven job completion, and runtime integration
for the SM8250 standalone JPEG encode hardware block.

> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
> index 0000000000000..626cd6130d9bc
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_dev.c
> @@ -0,0 +1,313 @@
[ ... ]
> +static int qcom_jpeg_probe(struct platform_device *pdev)
> +{
> +	const struct qcom_dev_resources *res;
> +	struct qcom_jenc_dev *jenc;
> +	int rc;
> +
> +	jenc = devm_kzalloc(&pdev->dev, sizeof(*jenc), GFP_KERNEL);

[Severity: High]
Can this lead to a use-after-free if the platform device is unbound while a
file descriptor remains open?

The jenc structure is allocated with devm_kzalloc here, tying its lifetime
to the platform device. However, fop_jpeg_file_release() dereferences jenc
upon file closure. If the device is unbound, devres will free the structure,
causing memory corruption when the file is later closed. V4L2 device state
should typically be tied to the video_device lifecycle using the release
callback.

> +	if (!jenc)
> +		return -ENOMEM;
[ ... ]
> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
> index 0000000000000..5553ba7571946
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_ops.c
[ ... ]
> +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);

[Severity: Medium]
Does a concurrent read-modify-write on the int_mask register drop mask
modifications?

jpeg_rw_bits() performs a non-atomic software read, modify, and write
sequence. jpeg_stop() modifies this register from the threaded IRQ handler
without locks, while jpeg_deinit() and jpeg_init() modify it holding
dev_mutex. This lack of synchronization allows interleaved operations to
silently drop mask updates.

> +}
> +
> +static int jpeg_deinit(struct qcom_jenc_dev *jenc)
> +{
> +	const struct qcom_jpeg_reg_offs *offs = jenc->res->hw_offs;
> +	unsigned long rtime;
> +
> +	jpeg_wo_bits(jenc, offs->int_clr, JMSK_IRQ_STATUS_ALL_BITS, JPEG_SET_U32);
> +	jpeg_rw_bits(jenc, offs->int_mask, JMSK_IRQ_STATUS_STOP_ACK, JPEG_SET_U32);
> +
> +	jpeg_wo_bits(jenc, offs->hw_cmd, JMSK_CMD_HW_STOP, 1);
> +
> +	reinit_completion(&jenc->stop_complete);

[Severity: Medium]
Does reinitializing the completion after issuing the stop command create a
race condition?

If the hardware processes the stop command quickly and the STOP_ACK
interrupt fires before reinit_completion() executes, the completion will
be cleared. This would cause the subsequent wait_for_completion_timeout()
to block for the full duration and fail.

> +	rtime = wait_for_completion_timeout(&jenc->stop_complete,
> +					    msecs_to_jiffies(JPEG_STOP_TIMEOUT_MS));
> +	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);
> +
> +	if (!rtime) {
> +		dev_err(jenc->dev, "encoder HW stop timeout\n");
> +		return -ETIME;
> +	}
> +
> +	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;
> +	size_t header_size;
> +	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) {

[Severity: Medium]
Does optimizing the DMI table upload based on context-local tracking cause
issues for interleaved streams?

The mem2mem hardware is globally shared and soft-reset before each job. If
two contexts have different quality settings, checking only the local
quality_programmed will result in skipping the hardware upload. This causes
the frame to be encoded using the wrong or reset DMI tables.

> +		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);
> +
> +	header_size = min_t(size_t, vb->planes[0].length, ectx->hdr_cache.size);
> +	header_size = qcom_jenc_header_emit(&ectx->hdr_cache, mptr,
> +					    (u32)header_size, (u16)q->vf.width,
> +					    (u16)q->vf.height);
> +
> +	dma += header_size;

[Severity: High]
Will this cause an out-of-bounds DMA write?

When userspace specifies explicit buffer properties via VIDIOC_CREATE_BUFS,
an early return in bop_jpeg_vb2_queue_setup() skips the logic that adds
JPEG_HEADER_MAX to the allocated buffer size. During encoding, this base
pointer is shifted forward by the header size, causing the hardware to write
past the end of the allocated physical memory.

> +	qcom_jenc_dqts_emit(&ectx->hdr_cache, mptr);
[ ... ]
> +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);

[Severity: High]
Can this cause IOMMU faults or memory corruption?

The queue is configured with vb2_dma_sg_memops, allowing non-contiguous
physical buffers. However, only the first segment's base address is extracted
via sg_dma_address() here. If the IOMMU splits a large frame into multiple
segments, the hardware is programmed with a single pointer and will linearly
overrun the first mapped IOVA segment into unrelated memory.

> +	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 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;
> +
> +		spin_lock_irqsave(&jenc->hw_lock, flags);
> +		ctx = jenc->actx;
> +		jenc->actx = NULL;
> +		spin_unlock_irqrestore(&jenc->hw_lock, flags);

[Severity: High]
Is it possible for the context to be freed while the threaded IRQ handler
is still processing it?

Here the global jenc->actx pointer is cleared under a spinlock, but the
context is processed locally after dropping the lock. Concurrently, if the
file is closed, fop_jpeg_file_release() sees jenc->actx is already NULL and
proceeds to completely destroy and kfree() the context. This leads to a
use-after-free when the IRQ handler completes its callback below.

> +
> +		if (ctx && ctx->bufq[JENC_DST_QUEUE].buff_id >= 0) {
> +			struct qcom_jenc_queue *dq = &ctx->bufq[JENC_DST_QUEUE];
> +			struct qcom_jpeg_buff *frame = &dq->buff[dq->buff_id];
> +			size_t out_size = jpeg_io_read(jenc, offs->enc_out_size);
> +
> +			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 + ctx->hdr_cache.size);
> +		}
> +
> +		jpeg_stop(jenc);

[Severity: High]
Can this cause an ABBA deadlock?

When an encode finishes, the threaded IRQ handler executes but waits to
acquire dev_mutex in the callback. Concurrently, userspace closing the stream
triggers bop_jpeg_vb2_stop_streaming(), which acquires dev_mutex and issues a
stop command. The stop sequence waits for an interrupt, but the IRQ handler
is threaded with IRQF_ONESHOT, meaning the hardware IRQ line remains masked.
This leads to a timeout, after which the hardware clocks are disabled. The
IRQ handler then eventually resumes and writes to unclocked hardware
registers in jpeg_stop().

> +	}
[ ... ]
> diff --git a/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
> index 0000000000000..7c0af616fda8e
> --- /dev/null
> +++ b/drivers/media/platform/qcom/jpeg/qcom_jenc_v4l2.c
[ ... ]
> +static void jpeg_v4l2_work_stop(struct jenc_context *ctx, enum vb2_buffer_state buff_state)
> +{
> +	bool was_stopping = ctx->is_stopping;
> +	struct vb2_v4l2_buffer *vb;
> +
> +	ctx->is_stopping = false;
> +
> +	/*
> +	 * Drain the CAPTURE queue.  If a V4L2_ENC_CMD_STOP was in progress
> +	 * (was_stopping), signal EOS on the last CAPTURE buffer by calling
> +	 * v4l2_m2m_last_buffer_done() with a zero-length payload instead of
> +	 * returning it as an error.  All remaining buffers are returned with
> +	 * buff_state (DONE or ERROR depending on the call site).
> +	 *
> +	 * v4l2_m2m_last_dst_buf() returns true when the queue will be empty
> +	 * after the current remove, i.e. this is the last buffer.
> +	 */
> +	while ((vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
> +		if (was_stopping && !v4l2_m2m_last_dst_buf(ctx->fh.m2m_ctx)) {

[Severity: Medium]
Is the condition for checking the last destination buffer inverted?

Negating v4l2_m2m_last_dst_buf() causes every buffer except the last one
to be marked as EOS with a zero-length payload. The final buffer gets
standard processing. This drops pending frames and violates V4L2 API
compliance for V4L2_ENC_CMD_STOP.

> +			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);
> +		}
> +	}
[ ... ]
> +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);
> +
> +	if (ectx->hw_acquired) {
> +		hw->hw_release(ectx, q);

[Severity: High]
Does this code return buffers to userspace before the hardware DMA has been
explicitly stopped?

jpeg_v4l2_work_stop() drains the capture and output queues and returns all
buffers to userspace. However, the hardware encoder isn't explicitly stopped
until hw_release() is called afterward. This allows userspace to free or
reuse the memory while the DMA engine is still executing, leading to memory
corruption.

> +		ectx->hw_acquired = false;
> +	}
> +
> +	mutex_unlock(&jenc->dev_mutex);
> +}
[ ... ]
> +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);

[Severity: High]
Does this cause a deadlock by acquiring a sleeping mutex in an atomic
context?

The mem2mem framework invokes the job_abort callback while holding the
job_spinlock, an atomic context. Acquiring dev_mutex here violates the
invariant of not sleeping inside a spinlock-protected section.

> +
> +	jpeg_v4l2_work_stop(ectx, VB2_BUF_STATE_ERROR);
> +
> +	mutex_unlock(&jenc->dev_mutex);
> +}
[ ... ]
> +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, "queue=%d size[%d]=%d\n", TYPE2QID(vq->type),
> +			pln, sizes[pln]);
> +	}
> +
> +	/*
> +	 * Reserve space for the JPEG header that the CPU writes into the
> +	 * CAPTURE buffer before the hardware DMA engine appends the bitstream.
> +	 * sizeimage covers the worst-case compressed bitstream; JPEG_HEADER_MAX
> +	 * is a separate, additional reservation for the header region.
> +	 */
> +	if (V4L2_TYPE_IS_CAPTURE(vq->type))
> +		sizes[0] += JPEG_HEADER_MAX;
> +
> +	return hw->queue_setup(ectx, TYPE2QID(vq->type));
> +}
[ ... ]
> +static int fop_jpeg_file_release(struct file *file)
> +{
> +	struct jenc_context *ectx = jpeg_file2ctx(file);
> +	struct v4l2_m2m_dev *m2m_dev = ectx->fh.m2m_ctx->m2m_dev;
> +	struct qcom_jenc_dev *jenc = ectx->jenc;
> +	unsigned long flags;
> +
> +	/*
> +	 * Ensure the threaded IRQ handler cannot dereference this context
> +	 * after it is freed.  Clear jenc->actx under hw_lock before
> +	 * cancelling work and releasing the m2m context.
> +	 */
> +	spin_lock_irqsave(&jenc->hw_lock, flags);
> +	if (jenc->actx == ectx)
> +		jenc->actx = NULL;
> +	spin_unlock_irqrestore(&jenc->hw_lock, flags);
> +
> +	cancel_work_sync(&ectx->stop_work);
> +	cancel_work_sync(&ectx->finish_work);
> +
> +	v4l2_m2m_ctx_release(ectx->fh.m2m_ctx);

[Severity: High]
Does cancelling the work before calling v4l2_m2m_ctx_release() lead to a
use-after-free?

Releasing the m2m context cascades into stop_streaming and
jpeg_v4l2_work_stop(), which reschedules finish_work. Because the
cancellation barrier was already passed, the work is pushed to the system
queue and will execute after the context is freed via kfree() below.

> +	v4l2_m2m_put(m2m_dev);
> +	v4l2_ctrl_handler_free(&ectx->ctrl_hdl);
> +	v4l2_fh_del(&ectx->fh, file);
> +	v4l2_fh_exit(&ectx->fh);
> +	kfree(ectx);
> +
> +	return 0;
> +}

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

^ permalink raw reply

* Re: [PATCH v2 1/3] dt-bindings: media: qcom: Add JPEG encoder binding
From: Krzysztof Kozlowski @ 2026-06-25 13:55 UTC (permalink / raw)
  To: Atanas Filipov, linux-media
  Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <20260625133828.3221781-2-atanas.filipov@oss.qualcomm.com>

On 25/06/2026 15:38, Atanas Filipov wrote:
> Add device-tree binding for the Qualcomm JPEG encoder hardware block
> present in SM8250 (Kona) SoCs.
> 
> The JPEG encoder is a standalone hardware IP within the camera subsystem
> that performs JPEG compression in memory-to-memory fashion.  It is
> separate from the CAMSS ISP pipeline and has its own register space,
> interrupt, clocks, power domain, IOMMU streams, and interconnect paths.
> 
> Properties documented:
> - compatible: qcom,sm8250-jenc
> - reg / reg-names: single MMIO region named "jpeg"
> - interrupts: single edge-triggered interrupt
> - clocks / clock-names: Common clocks and JPEG core clock
> - power-domains: TITAN_TOP_GDSC common domain
> - iommus: two SMMU stream IDs for JPEG pixel and JPEG DMA processing
> - interconnects / interconnect-names
> - OPP table mapping performance levels to clock frequencies

Drop, since when commits have such text? Why are you describing diff?


> 
> Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
> ---
>  .../bindings/media/qcom,jpeg-encoder.yaml     | 135 ++++++++++++++++++
>  1 file changed, 135 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..ab8d8951d21f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml

Nothing improved.

You actually ignored all the comments from me and at least one more
comment from other emails provided to you.

This is not acceptable.

NAK


Best regards,
Krzysztof

^ permalink raw reply

* Re: [PATCH V2 1/8] PCI: imx6: Add skip_pwrctrl_off flag support
From: Manivannan Sadhasivam @ 2026-06-25 13:57 UTC (permalink / raw)
  To: Sherry Sun
  Cc: Frank Li (OSS), Sherry Sun (OSS), robh@kernel.org,
	krzk+dt@kernel.org, conor+dt@kernel.org, Frank Li,
	s.hauer@pengutronix.de, kernel@pengutronix.de, festevam@gmail.com,
	Amitkumar Karwar, Neeraj Sanjay Kale, marcel@holtmann.org,
	luiz.dentz@gmail.com, Hongxing Zhu, l.stach@pengutronix.de,
	lpieralisi@kernel.org, kwilczynski@kernel.org,
	bhelgaas@google.com, brgl@kernel.org, imx@lists.linux.dev,
	linux-pci@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-bluetooth@vger.kernel.org, linux-pm@vger.kernel.org
In-Reply-To: <VI0PR04MB12114BCBD405505888063284492EC2@VI0PR04MB12114.eurprd04.prod.outlook.com>

On Thu, Jun 25, 2026 at 07:25:46AM +0000, Sherry Sun wrote:
> > Subject: Re: [PATCH V2 1/8] PCI: imx6: Add skip_pwrctrl_off flag support
> > 
> > On Wed, Jun 24, 2026 at 07:09:26AM +0000, Sherry Sun wrote:
> > > > Subject: Re: [PATCH V2 1/8] PCI: imx6: Add skip_pwrctrl_off flag
> > > > support
> > > >
> > > > On Tue, Jun 23, 2026 at 11:07:28AM +0800, Sherry Sun (OSS) wrote:
> > > > > From: Sherry Sun <sherry.sun@nxp.com>
> > > > >
> > > > > Use dw_pcie_rp::skip_pwrctrl_off to avoid powering off devices
> > > > > during suspend to preserve wakeup capability of the devices and
> > > > > also not to power on the devices in the init path.
> > > > > This allows controller power-off to be skipped when some devices(e.g.
> > > > > M.2 cards key E without auxiliary power) required to support PCIe
> > > > > L2 link state and wake-up mechanisms.
> > > > >
> > > > > Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
> > > > > ---
> > > > >  drivers/pci/controller/dwc/pci-imx6.c | 36
> > > > > +++++++++++++++++----------
> > > > >  1 file changed, 23 insertions(+), 13 deletions(-)
> > > > >
> > > > > diff --git a/drivers/pci/controller/dwc/pci-imx6.c
> > > > > b/drivers/pci/controller/dwc/pci-imx6.c
> > > > > index 0fa716d1ed75..ff5a9565dbbf 100644
> > > > > --- a/drivers/pci/controller/dwc/pci-imx6.c
> > > > > +++ b/drivers/pci/controller/dwc/pci-imx6.c
> > > > > @@ -1382,16 +1382,20 @@ static int imx_pcie_host_init(struct
> > > > > dw_pcie_rp
> > > > *pp)
> > > > >  		}
> > > > >  	}
> > > > >
> > > > > -	ret = pci_pwrctrl_create_devices(dev);
> > > > > -	if (ret) {
> > > > > -		dev_err(dev, "failed to create pwrctrl devices\n");
> > > > > -		goto err_reg_disable;
> > > > > +	if (!pci->suspended) {
> > > > > +		ret = pci_pwrctrl_create_devices(dev);
> > > >
> > > > Is possible move pci_pwrctrl_create_devices() of
> > > > pci_pwrctrl_create_devices
> > > >
> > > > and call it direct at probe() function, like other regulator_get function.
> > > >
> > >
> > > Hi Frank,
> > > That makes sense. However, if we move pci_pwrctrl_create_devices () to
> > > probe(), we may need to add the following goto err_pwrctrl_destroy
> > > path in imx_pcie_probe() to properly handle errors from
> > > pci_pwrctrl_power_on_devices(), is that acceptable?
> > 
> > Can you add a API devm_pci_pwrctrl_create_devices() ?
> > 
> 
> Hi Frank, we cannot unconditionally destroy the pwrctrl devices
> when probing fails by using devm API.
> Since we need to check the return value of
> pci_pwrctrl_power_on_devices() for example EPROBE_DEFER to decide
> whether to destroy the pwrctrl devices to avoid the deferred probe loop.
> 
> You can find more related discussion here.
> https://lore.kernel.org/all/tutxwjciedqoje5wxvtin4h637auni5zzpvb7rtfg4uticxoux@yfl6xg7oht7t/
> 

Yes. Ideally we should try to do a blocking wait in the pwrctrl core until all
the pwrctrl drivers are probed, instead of deferring probe. Hopefully, I'll get
to it soon.

- Mani

-- 
மணிவண்ணன் சதாசிவம்

^ permalink raw reply

* Re: [PATCH v2 1/3] dt-bindings: media: qcom: Add JPEG encoder binding
From: Atanas Filipov @ 2026-06-25 14:03 UTC (permalink / raw)
  To: Krzysztof Kozlowski, linux-media
  Cc: bod, mchehab, robh, krzk+dt, conor+dt, andersson, konradybcio,
	linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <e65358b0-b978-4672-9691-705897bcf209@kernel.org>

On 6/25/2026 4:55 PM, Krzysztof Kozlowski wrote:
> On 25/06/2026 15:38, Atanas Filipov wrote:
>> Add device-tree binding for the Qualcomm JPEG encoder hardware block
>> present in SM8250 (Kona) SoCs.
>>
>> The JPEG encoder is a standalone hardware IP within the camera subsystem
>> that performs JPEG compression in memory-to-memory fashion.  It is
>> separate from the CAMSS ISP pipeline and has its own register space,
>> interrupt, clocks, power domain, IOMMU streams, and interconnect paths.
>>
>> Properties documented:
>> - compatible: qcom,sm8250-jenc
>> - reg / reg-names: single MMIO region named "jpeg"
>> - interrupts: single edge-triggered interrupt
>> - clocks / clock-names: Common clocks and JPEG core clock
>> - power-domains: TITAN_TOP_GDSC common domain
>> - iommus: two SMMU stream IDs for JPEG pixel and JPEG DMA processing
>> - interconnects / interconnect-names
>> - OPP table mapping performance levels to clock frequencies
> 
> Drop, since when commits have such text? Why are you describing diff?
> 
> 
>>
>> Signed-off-by: Atanas Filipov <atanas.filipov@oss.qualcomm.com>
>> ---
>>   .../bindings/media/qcom,jpeg-encoder.yaml     | 135 ++++++++++++++++++
>>   1 file changed, 135 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..ab8d8951d21f
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/qcom,jpeg-encoder.yaml
> 
> Nothing improved.
> 
> You actually ignored all the comments from me and at least one more
> comment from other emails provided to you.
> 
> This is not acceptable.
> 
> NAK
> 
> 
> Best regards,
> Krzysztof

Hi Krzysztof,

You are right, I apologize. I missed several comments.

I will address them properly in v3.

Regards,
Atanas

^ permalink raw reply

* [PATCH 0/6] Add support for IPQ5018 Bluetooth
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
  To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
	Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
	Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
	Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
	Mathieu Poirier, Philipp Zabel
  Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
	ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
	George Moussalem

Hello,

This patch series introduces Bluetooth support for IPQ5018.

Bluetooth firmware on the IPQ5018 platform runs on the M0 co-processor.
The firmware is loaded by the host into a dedicated reserved memory
carveout and authenticated by TrustZone. A Secure Channel Manager (SCM)
call safely brings the peripheral core out of reset.

A shared memory ring buffer topology handles runtime data frame
transport between the host APSS and the M0 core. An outgoing APCS IPC
bit and an incoming GIC interrupt handle host/guest signaling.

This series has been tested and verified on various IPQ5018 router
boards utilizing firmware extracted from GPL distributions, using both
mdt and mbn file formats.

[   14.781511] Bluetooth: hci0: QCA Product ID   :0x00000016
[   14.781583] Bluetooth: hci0: QCA SOC Version  :0x20180100
[   14.785926] Bluetooth: hci0: QCA ROM Version  :0x00000100
[   14.791546] Bluetooth: hci0: QCA Patch Version:0x00003ded
[   14.796698] Bluetooth: hci0: QCA controller version 0x01000100
[   14.802217] Bluetooth: hci0: QCA Downloading qca/mpnv10.bin
[   16.393850] Bluetooth: hci0: QCA Build Info: BTFW.MAPLE.1.0.0-00102-MPL_ROM_PATCHZ-1

Best regards,
George Moussalem

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
George Moussalem (6):
      dt-bindings: remoteproc: document M0 Bluetooth Subsystem secure PIL
      remoteproc: qcom: Add M0 BTSS secure PIL driver
      Bluetooth: btqca: Add IPQ5018 support
      dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
      Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
      arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support

 .../bindings/net/bluetooth/qcom,ipq5018-bt.yaml    |  63 ++
 .../bindings/remoteproc/qcom,m0-btss-pil.yaml      |  72 ++
 arch/arm64/boot/dts/qcom/ipq5018.dtsi              |  34 +-
 drivers/bluetooth/Kconfig                          |  11 +
 drivers/bluetooth/Makefile                         |   1 +
 drivers/bluetooth/btqca.c                          |  16 +
 drivers/bluetooth/btqca.h                          |   3 +
 drivers/bluetooth/btqcomipc.c                      | 939 +++++++++++++++++++++
 drivers/remoteproc/Kconfig                         |  12 +
 drivers/remoteproc/Makefile                        |   1 +
 drivers/remoteproc/qcom_m0_btss_pil.c              | 261 ++++++
 11 files changed, 1412 insertions(+), 1 deletion(-)
---
base-commit: 4d1ab324fcb7d20df5a071edb0304461846fdc12
change-id: 20260625-ipq5018-bluetooth-06ff66c9d753
prerequisite-message-id: <20260612-block-as-nvmem-v5-0-95e0b30fff90@oss.qualcomm.com>
prerequisite-patch-id: 6ce8686c1683f468d86b4502f5ec9d19c392a382
prerequisite-patch-id: e362f7fcbacff716b7ef720e6780786a7d88c013
prerequisite-patch-id: 9168930e40551e842c8171d5433a6f39ad4b78a4
prerequisite-patch-id: 64fecfbd1e085d7d2ab0ae23295ca34ec8e14c5e
prerequisite-patch-id: 566804aaa690ee9aa285d0fd75fd16d94fbadebf
prerequisite-patch-id: dc18bec338f54b3051f4523f9d1d3c0566a20ccd
prerequisite-patch-id: b6b3eb46429936ab49423d295433daf47981db0f
prerequisite-patch-id: 75caa99e3bbcdf41b6462b9f5f703bea1d4a65fa
prerequisite-patch-id: 35e9968f482f78ca233eb0306d9c5fdbff093175

Best regards,
-- 
George Moussalem <george.moussalem@outlook.com>



^ permalink raw reply

* [PATCH 1/6] dt-bindings: remoteproc: document M0 Bluetooth Subsystem secure PIL
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
  To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
	Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
	Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
	Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
	Mathieu Poirier, Philipp Zabel
  Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
	ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
	George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>

From: George Moussalem <george.moussalem@outlook.com>

Document the M0 Bluetooth Subsystem remote processor core found in the
Qualcomm IPQ5018 SoC. Firmware loaded is authenticated via TrustZone.
The firmware running on the M0 core provides bluetooth functionality.

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 .../bindings/remoteproc/qcom,m0-btss-pil.yaml      | 72 ++++++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,m0-btss-pil.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,m0-btss-pil.yaml
new file mode 100644
index 000000000000..397bb6815d71
--- /dev/null
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,m0-btss-pil.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/remoteproc/qcom,m0-btss-pil.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm M0 BTSS Peripheral Image Loader
+
+maintainers:
+  - George Moussalem <george.moussalem@outlook.com>
+
+description:
+  Qualcomm M0 BTSS Peripheral Secure Image Loader loads firmware and powers up
+  the M0 BTSS remote processor core on the Qualcomm IPQ5018 SoC.
+
+properties:
+  compatible:
+    enum:
+      - qcom,ipq5018-btss-pil
+
+  firmware-name:
+    maxItems: 1
+    description: Firmware name for the M0 Bluetooth Subsystem core
+
+  clocks:
+    items:
+      - description: M0 BTSS low power oscillator clock
+
+  clock-names:
+    items:
+      - const: btss_lpo_clk
+
+  memory-region:
+    items:
+      - description: M0 BTSS reserved memory carveout
+
+  resets:
+    items:
+      - description: M0 BTSS reset
+
+  reset-names:
+    items:
+      - const: btss_reset
+
+required:
+  - compatible
+  - firmware-name
+  - clocks
+  - clock-names
+  - resets
+  - reset-names
+  - memory-region
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/qcom,gcc-ipq5018.h>
+    #include <dt-bindings/reset/qcom,gcc-ipq5018.h>
+
+    remoteproc {
+      compatible = "qcom,ipq5018-btss-pil";
+
+      firmware-name = "qca/bt_fw_patch.mbn";
+
+      clocks = <&gcc GCC_BTSS_LPO_CLK>;
+      clock-names = "btss_lpo_clk";
+      resets = <&gcc GCC_BTSS_BCR>;
+      reset-names = "btss_reset";
+
+      memory-region = <&btss_region>;
+    };

-- 
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