Devicetree
 help / color / mirror / Atom feed
* [PATCH v4 0/4] iio: adc: Add support for LTC2378 and similar ADCs
From: Marcelo Schmitt @ 2026-06-25 14:34 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel
  Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
	conor+dt, julianbraha, marcelo.schmitt1

This patch series adds support for LTC2378 and similar low noise, low power,
high speed, successive approximation register (SAR) ADCs. These ADCs are similar
among each other, varying mainly on the amount of precision bits, maximum sample
rate, and input configuration (either fully differential or pseudo-differential).

Patch 1 adds device tree documentation for LTC2378.

Patch 2 enables single-shot sample read with a GPIO connected to the LTC2378 CNV pin.

Patch 3 enables high-speed data captures with SPI offloading.
The setup is similar to AD4030, with a specialized PWM generator being used both
for SPI offload triggering and conversion start signaling.

Patch 4 enables running buffered data captures without SPI offloading.

Even though these parts are somewhat similar to AD4000, the wiring configuration
for LTC parts is different as well as the available HDL for high speed sample
rate mode. Because of that, I propose creating a new device driver for
supporting LTC2378-like devices.

Specifications can be found at:
https://www.analog.com/media/en/technical-documentation/data-sheets/233818fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/236416fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/236418f.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/236716fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/236718f.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/236816f.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/236818f.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/236918fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237016fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237616fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237618fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237620fb.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237716fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237718fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237720fb.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237816fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237818fa.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237820fb.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/237918fb.pdf
https://www.analog.com/media/en/technical-documentation/data-sheets/238016fb.pdf

Despite the initial version of the LTC2378 driver had been developed by
Ioan-Daniel. The current version has been greatly rewritten such that very
little remained from that initial version. Due to that, having Ioan-Daniel in
the author list now seems inaccurate.

Previous submissions:
  v3: https://lore.kernel.org/linux-iio/cover.1781661028.git.marcelo.schmitt@analog.com/
  v2: https://lore.kernel.org/linux-iio/cover.1779976379.git.marcelo.schmitt@analog.com/
  v1: https://lore.kernel.org/linux-iio/cover.1779117444.git.marcelo.schmitt1@gmail.com/

Change log v3 -> v4:
[DT]
- Added PWM property.
- Documented LTC2338-18 specific refin-supply.
- Picked up Conor's review tag.
[IIO]
- Dropped DMAengine buffer changes.
- Reworked the driver to make IIO channels static according to v3 feedback.
- Updated to use default 8 bits_per_word for non-offloaded transfers.
- Handled refin voltage reference supply for LTC2338.
- Used spi_bpw_to_bytes() where applicable.

With best regards,
Marcelo

Marcelo Schmitt (4):
  dt-bindings: iio: adc: Add ltc2378
  iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
  iio: adc: ltc2378: Enable high-speed data capture
  iio: adc: ltc2378: Enable triggered buffer data capture

 .../bindings/iio/adc/adi,ltc2378.yaml         | 185 +++++
 MAINTAINERS                                   |   8 +
 drivers/iio/adc/Kconfig                       |  19 +
 drivers/iio/adc/Makefile                      |   1 +
 drivers/iio/adc/ltc2378.c                     | 778 ++++++++++++++++++
 5 files changed, 991 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
 create mode 100644 drivers/iio/adc/ltc2378.c


base-commit: cc746297b23e89bd5df9f91f3a0ca209e8991763
-- 
2.53.0


^ permalink raw reply

* [PATCH 3/3] dt-bindings: gpu: img,powervr-*: Add maintainer entries
From: Matt Coster @ 2026-06-25 14:03 UTC (permalink / raw)
  To: imagination, linux-kernel
  Cc: Alessio Belle, Luigi Santivetti, Frank Binns, Brajesh Gupta,
	Alexandru Dadu, dri-devel, devicetree, Matt Coster
In-Reply-To: <20260625-maintainer-updates-v1-0-35112b2f038e@imgtec.com>

These entries already exist in MAINTAINERS, add them here so downstream
users of the bindings also see them.

Signed-off-by: Matt Coster <matt.coster@imgtec.com>
---
 Documentation/devicetree/bindings/gpu/img,powervr-rogue.yaml | 4 +++-
 Documentation/devicetree/bindings/gpu/img,powervr-sgx.yaml   | 4 +++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/gpu/img,powervr-rogue.yaml b/Documentation/devicetree/bindings/gpu/img,powervr-rogue.yaml
index a1f54dbae3f3..91e4ff61b394 100644
--- a/Documentation/devicetree/bindings/gpu/img,powervr-rogue.yaml
+++ b/Documentation/devicetree/bindings/gpu/img,powervr-rogue.yaml
@@ -8,7 +8,9 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
 title: Imagination Technologies PowerVR and IMG Rogue GPUs
 
 maintainers:
-  - Frank Binns <frank.binns@imgtec.com>
+  - Matt Coster <matt.coster@imgtec.com>
+  - Alessio Belle <alessio.belle@imgtec.com>
+  - Luigi Santivetti <luigi.santivetti@imgtec.com>
 
 properties:
   compatible:
diff --git a/Documentation/devicetree/bindings/gpu/img,powervr-sgx.yaml b/Documentation/devicetree/bindings/gpu/img,powervr-sgx.yaml
index f5898b04381c..3e7df7344430 100644
--- a/Documentation/devicetree/bindings/gpu/img,powervr-sgx.yaml
+++ b/Documentation/devicetree/bindings/gpu/img,powervr-sgx.yaml
@@ -9,7 +9,9 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
 title: Imagination Technologies PowerVR SGX GPUs
 
 maintainers:
-  - Frank Binns <frank.binns@imgtec.com>
+  - Matt Coster <matt.coster@imgtec.com>
+  - Alessio Belle <alessio.belle@imgtec.com>
+  - Luigi Santivetti <luigi.santivetti@imgtec.com>
 
 properties:
   compatible:

-- 
2.48.1


^ permalink raw reply related

* [PATCH 2/3] MAINTAINERS: Update imagination maintainers
From: Matt Coster @ 2026-06-25 14:03 UTC (permalink / raw)
  To: imagination, linux-kernel
  Cc: Alessio Belle, Luigi Santivetti, Frank Binns, Brajesh Gupta,
	Alexandru Dadu, dri-devel, devicetree, Matt Coster
In-Reply-To: <20260625-maintainer-updates-v1-0-35112b2f038e@imgtec.com>

New people are taking up maintainership roles within the team.

Signed-off-by: Matt Coster <matt.coster@imgtec.com>
---
 MAINTAINERS | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index b45e60524762..fe02d1087ab2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12567,8 +12567,9 @@ S:	Orphan
 F:	drivers/media/rc/img-ir/
 
 IMGTEC POWERVR DRM DRIVER
-M:	Frank Binns <frank.binns@imgtec.com>
 M:	Matt Coster <matt.coster@imgtec.com>
+M:	Alessio Belle <alessio.belle@imgtec.com>
+M:	Luigi Santivetti <luigi.santivetti@imgtec.com>
 L:	imagination@lists.freedesktop.org
 S:	Supported
 Q:	https://patchwork.freedesktop.org/project/imagination/list/

-- 
2.48.1


^ permalink raw reply related

* [PATCH 1/3] MAINTAINERS: Update imagination details
From: Matt Coster @ 2026-06-25 14:03 UTC (permalink / raw)
  To: imagination, linux-kernel
  Cc: Alessio Belle, Luigi Santivetti, Frank Binns, Brajesh Gupta,
	Alexandru Dadu, dri-devel, devicetree, Matt Coster
In-Reply-To: <20260625-maintainer-updates-v1-0-35112b2f038e@imgtec.com>

There's a bunch of useful information missing from this entry, flesh it
out and simplify the dt-bindings pattern while we're at it.

Signed-off-by: Matt Coster <matt.coster@imgtec.com>
---
 MAINTAINERS | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 987635948cde..b45e60524762 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12569,10 +12569,13 @@ F:	drivers/media/rc/img-ir/
 IMGTEC POWERVR DRM DRIVER
 M:	Frank Binns <frank.binns@imgtec.com>
 M:	Matt Coster <matt.coster@imgtec.com>
+L:	imagination@lists.freedesktop.org
 S:	Supported
+Q:	https://patchwork.freedesktop.org/project/imagination/list/
+B:	https://gitlab.freedesktop.org/imagination/linux/-/issues
+C:	irc://irc.oftc.net/powervr
 T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
-F:	Documentation/devicetree/bindings/gpu/img,powervr-rogue.yaml
-F:	Documentation/devicetree/bindings/gpu/img,powervr-sgx.yaml
+F:	Documentation/devicetree/bindings/gpu/img,powervr-*.yaml
 F:	Documentation/gpu/imagination/
 F:	drivers/gpu/drm/ci/xfails/powervr*
 F:	drivers/gpu/drm/imagination/

-- 
2.48.1


^ permalink raw reply related

* [PATCH 0/3] drm/imagination: Maintainer updates
From: Matt Coster @ 2026-06-25 14:03 UTC (permalink / raw)
  To: imagination, linux-kernel
  Cc: Alessio Belle, Luigi Santivetti, Frank Binns, Brajesh Gupta,
	Alexandru Dadu, dri-devel, devicetree, Matt Coster

We've got some new people stepping up to help out with maintainership of
the imagination driver, so let's take this opportunity to unify the
maintainer list where it appears across multiple files.

There are also some new resources (mailing list, patchwork, IRC) that
didn't previously exist and had not yet been added, so let's do that now
as well.

Signed-off-by: Matt Coster <matt.coster@imgtec.com>
---
Matt Coster (3):
      MAINTAINERS: Update imagination details
      MAINTAINERS: Update imagination maintainers
      dt-bindings: gpu: img,powervr-*: Add maintainer entries

 Documentation/devicetree/bindings/gpu/img,powervr-rogue.yaml |  4 +++-
 Documentation/devicetree/bindings/gpu/img,powervr-sgx.yaml   |  4 +++-
 MAINTAINERS                                                  | 10 +++++++---
 3 files changed, 13 insertions(+), 5 deletions(-)
---
base-commit: 60b5fa6edfef867322fce7c8306e5c4b46211be7
change-id: 20260624-maintainer-updates-bfc189254555


^ permalink raw reply

* Re: [PATCH 2/6] remoteproc: qcom: Add M0 BTSS secure PIL driver
From: George Moussalem @ 2026-06-25 14:24 UTC (permalink / raw)
  To: Philipp Zabel, 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
  Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
	ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc
In-Reply-To: <439f76c3fcafdfb91cca426fcae17ef776776eab.camel@pengutronix.de>

Thanks, that was quick!

On 6/25/26 18:18, Philipp Zabel wrote:
> On Do, 2026-06-25 at 18:10 +0400, George Moussalem via B4 Relay wrote:
>> From: George Moussalem <george.moussalem@outlook.com>
>>
>> Add support to bring up the M0 core of the bluetooth subsystem found in
>> the IPQ5018 SoC.
>>
>> The signed firmware loaded is authenticated by TrustZone. If successful,
>> the M0 core boots the firmware and the peripheral is taken out of reset
>> using a Secure Channel Manager call to TrustZone.
>>
>> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
>> ---
>>  drivers/remoteproc/Kconfig            |  12 ++
>>  drivers/remoteproc/Makefile           |   1 +
>>  drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++++++++++++++++++++++++++++++
>>  3 files changed, 274 insertions(+)
>>
> [...]
>> diff --git a/drivers/remoteproc/qcom_m0_btss_pil.c b/drivers/remoteproc/qcom_m0_btss_pil.c
>> new file mode 100644
>> index 000000000000..7168e270e4d4
>> --- /dev/null
>> +++ b/drivers/remoteproc/qcom_m0_btss_pil.c
>> @@ -0,0 +1,261 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) 2026 The Linux Foundation. All rights reserved.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/elf.h>
>> +#include <linux/firmware/qcom/qcom_scm.h>
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/of_reserved_mem.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/reset.h>
>> +#include <linux/soc/qcom/mdt_loader.h>
>> +
>> +#include "qcom_common.h"
>> +
>> +#define BTSS_PAS_ID	0xc
>> +
>> +struct m0_btss {
>> +	struct device *dev;
>> +	phys_addr_t mem_phys;
>> +	phys_addr_t mem_reloc;
>> +	void __iomem *mem_region;
>> +	size_t mem_size;
>> +	struct reset_control *btss_reset;
> 
> Why is this stored here? It doesn't seem to be used.

will remove it and use devm_reset_control_get_exclusive_deasserted as
suggested below.

> 
> [...]
>> +static int m0_btss_pil_probe(struct platform_device *pdev)
>> +{
>> +	// struct reset_control *btss_reset;
> 
> It looks like this shouldn't be commented out.
> 
>> +	struct device *dev = &pdev->dev;
>> +	const char *fw_name = NULL;
>> +	struct m0_btss *desc;
>> +	struct clk *lpo_clk;
>> +	struct rproc *rproc;
>> +	int ret;
>> +
>> +	ret = of_property_read_string(dev->of_node, "firmware-name",
>> +				      &fw_name);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	rproc = devm_rproc_alloc(dev, "m0btss", &m0_btss_ops,
>> +				 fw_name, sizeof(*desc));
>> +	if (!rproc) {
>> +		dev_err(dev, "failed to allocate rproc\n");
>> +		return -ENOMEM;
>> +	}
>> +
>> +	desc = rproc->priv;
>> +	desc->dev = dev;
>> +
>> +	ret = m0_btss_alloc_memory_region(desc);
>> +	if (ret)
>> +		return ret;
>> +
>> +	lpo_clk = devm_clk_get_enabled(dev, "btss_lpo_clk");
>> +	if (IS_ERR(lpo_clk))
>> +		return dev_err_probe(dev, PTR_ERR(lpo_clk),
>> +				     "Failed to get lpo clock\n");
>> +
>> +	desc->btss_reset = devm_reset_control_get(dev, "btss_reset");
> 
> Please use devm_reset_control_get_exclusive() directly.
> 
>> +	if (IS_ERR_OR_NULL(desc->btss_reset))
>> +		return dev_err_probe(dev, PTR_ERR(desc->btss_reset),
>> +				     "unable to acquire btss_reset\n");
>> +
>> +	ret = reset_control_deassert(desc->btss_reset);
>> +	if (ret)
>> +		return dev_err_probe(rproc->dev.parent, ret,
>> +				     "Failed to deassert reset\n");
> 
> Shouldn't this be asserted on remove? Otherwise after an unbind/bind
> cycle probe will enable the clock with reset already deasserted.
> That may or may not be problematic.
> 
> Consider using devm_reset_control_get_exclusive_deasserted().
> 
> 
> regards
> Philipp

Regards,
George


^ permalink raw reply

* Re: [PATCH v2 4/4] iio: dac: ad3530r: Add support for AD3532R/AD3532
From: Andy Shevchenko @ 2026-06-25 14:22 UTC (permalink / raw)
  To: Paller, Kim Seer
  Cc: Jonathan Cameron, David Lechner, Sa, Nuno, Andy Shevchenko,
	Hennerich, Michael, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-iio@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux, devicetree@vger.kernel.org
In-Reply-To: <PH0PR03MB7141524D6546CD0FF1EF43FAF9EC2@PH0PR03MB7141.namprd03.prod.outlook.com>

On Thu, Jun 25, 2026 at 10:07:47AM +0000, Paller, Kim Seer wrote:
> > From: Jonathan Cameron <jic23@kernel.org>
> > Sent: Monday, June 22, 2026 12:46 AM
> > On Mon, 15 Jun 2026 13:05:44 +0300
> > Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> > > On Mon, Jun 15, 2026 at 02:20:18PM +0800, Kim Seer Paller wrote:

...

> > > > +#define AD3532R_MAX_REG_ADDR			0x30F9
> > Whilst we are here, Sashiko thinks there is an off by one on that value as it's
> > the lower of the two registers that make up channel 15.
> > https://urldefense.com/v3/__https://sashiko.dev/*/patchset/20260615-iio-
> > ad3532r-support-v2-0-
> > 84a0af8b83fa*40analog.com__;IyU!!A3Ni8CS0y2Y!88afCOStwucx32wuoeR
> > SyZ9GpkZge9YDw5_PIMAf7SLs3OLykUC_qNRDUCnRw7wTwsxiIT1V-
> > R8sH17sTg$
> > It also suggests an existing bug that it would be good to look into.
> 
> I don't think it's off-by-one. INPUT_CHn registers are listed by LSB, so
> channel 15 is 0x30F8 (LSB) / 0x30F9 (MSB).  The driver addresses the MSB and
> the part defaults to descending mode, so the access goes 0x30F9 -> 0x30F8.
> 0x30F9 is also the highest valid address per the datasheet, so max_register
> looks correct same for AD3530R's 0xF9.  Does that match our understanding, or
> am I missing a case?

If it's the value for .max_register in regmap configuration, then it's fine.

> > > Hmm... I dunno if it's better to sort by values (so the "bank" 0 goes
> > > together followed by "bank" 1). Jonathan, what's your preference here?
> > Nuno, David?
> > That is how people will typically check them vs the datasheet so I agree with
> > numeric order.  Maybe with a comment at the top about there effectively
> > being two banks. Many of the registers are effectively copies for the new
> > channels but not all of them, so a macro approach would probably be even
> > more confusing.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH 2/6] remoteproc: qcom: Add M0 BTSS secure PIL driver
From: Philipp Zabel @ 2026-06-25 14:18 UTC (permalink / raw)
  To: george.moussalem, 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
  Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
	ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc
In-Reply-To: <20260625-ipq5018-bluetooth-v1-2-d999be0e04f7@outlook.com>

On Do, 2026-06-25 at 18:10 +0400, George Moussalem via B4 Relay wrote:
> From: George Moussalem <george.moussalem@outlook.com>
> 
> Add support to bring up the M0 core of the bluetooth subsystem found in
> the IPQ5018 SoC.
> 
> The signed firmware loaded is authenticated by TrustZone. If successful,
> the M0 core boots the firmware and the peripheral is taken out of reset
> using a Secure Channel Manager call to TrustZone.
> 
> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
> ---
>  drivers/remoteproc/Kconfig            |  12 ++
>  drivers/remoteproc/Makefile           |   1 +
>  drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++++++++++++++++++++++++++++++
>  3 files changed, 274 insertions(+)
> 
[...]
> diff --git a/drivers/remoteproc/qcom_m0_btss_pil.c b/drivers/remoteproc/qcom_m0_btss_pil.c
> new file mode 100644
> index 000000000000..7168e270e4d4
> --- /dev/null
> +++ b/drivers/remoteproc/qcom_m0_btss_pil.c
> @@ -0,0 +1,261 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2026 The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/elf.h>
> +#include <linux/firmware/qcom/qcom_scm.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/of_reserved_mem.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +#include <linux/soc/qcom/mdt_loader.h>
> +
> +#include "qcom_common.h"
> +
> +#define BTSS_PAS_ID	0xc
> +
> +struct m0_btss {
> +	struct device *dev;
> +	phys_addr_t mem_phys;
> +	phys_addr_t mem_reloc;
> +	void __iomem *mem_region;
> +	size_t mem_size;
> +	struct reset_control *btss_reset;

Why is this stored here? It doesn't seem to be used.

[...]
> +static int m0_btss_pil_probe(struct platform_device *pdev)
> +{
> +	// struct reset_control *btss_reset;

It looks like this shouldn't be commented out.

> +	struct device *dev = &pdev->dev;
> +	const char *fw_name = NULL;
> +	struct m0_btss *desc;
> +	struct clk *lpo_clk;
> +	struct rproc *rproc;
> +	int ret;
> +
> +	ret = of_property_read_string(dev->of_node, "firmware-name",
> +				      &fw_name);
> +	if (ret < 0)
> +		return ret;
> +
> +	rproc = devm_rproc_alloc(dev, "m0btss", &m0_btss_ops,
> +				 fw_name, sizeof(*desc));
> +	if (!rproc) {
> +		dev_err(dev, "failed to allocate rproc\n");
> +		return -ENOMEM;
> +	}
> +
> +	desc = rproc->priv;
> +	desc->dev = dev;
> +
> +	ret = m0_btss_alloc_memory_region(desc);
> +	if (ret)
> +		return ret;
> +
> +	lpo_clk = devm_clk_get_enabled(dev, "btss_lpo_clk");
> +	if (IS_ERR(lpo_clk))
> +		return dev_err_probe(dev, PTR_ERR(lpo_clk),
> +				     "Failed to get lpo clock\n");
> +
> +	desc->btss_reset = devm_reset_control_get(dev, "btss_reset");

Please use devm_reset_control_get_exclusive() directly.

> +	if (IS_ERR_OR_NULL(desc->btss_reset))
> +		return dev_err_probe(dev, PTR_ERR(desc->btss_reset),
> +				     "unable to acquire btss_reset\n");
> +
> +	ret = reset_control_deassert(desc->btss_reset);
> +	if (ret)
> +		return dev_err_probe(rproc->dev.parent, ret,
> +				     "Failed to deassert reset\n");

Shouldn't this be asserted on remove? Otherwise after an unbind/bind
cycle probe will enable the clock with reset already deasserted.
That may or may not be problematic.

Consider using devm_reset_control_get_exclusive_deasserted().


regards
Philipp

^ permalink raw reply

* Re: [PATCH v6 0/9] media: add new API simple 1to1 subdev register and add imx parallel camera support
From: Frank Li @ 2026-06-25 14:17 UTC (permalink / raw)
  To: Sakari Ailus, Mauro Carvalho Chehab, Michael Riesch,
	Laurent Pinchart, Frank Li, Martin Kepplinger-Novakovic,
	Rui Miguel Silva, Purism Kernel Team, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam
  Cc: linux-media, linux-kernel, imx, Guoniu Zhou, devicetree,
	linux-arm-kernel, Alice Yuan, Robert Chiras, Zhipeng Wang,
	Krzysztof Kozlowski
In-Reply-To: <20260624-imx8qxp_pcam-v6-0-4b3f45920d2f@nxp.com>

On Wed, Jun 24, 2026 at 04:37:47PM -0400, Frank.Li@oss.nxp.com wrote:
> Base on patches "media: add and use fwnode_graph_for_each_endpoint_scoped()"
> https://lore.kernel.org/imx/20260624200237.GJ851255@killaraus.ideasonboard.com/T/#m7969735b6c236c6b3abc16b9f3f55ec0488dbe89
>
> This patches base on previous' thread "media: imx8qxp: add parallel camera
> support".
>
> Add new API media_async_register_subdev_1to1() to simple 1to1 subdev
> register.

typo here, should be media_async_register_subdev().

fwnode graphic, there two mehtod to connect nodes together.

method 1:

port@0
{
	endpoint@0{
		-> sensor 0
	}

	endpoint@1{
		-> sensor 1
	}
}

method 2:

port@0
{
	endpoint {
		-> sensor 0
	}
}

port@1 {
	endpoint {
		-> sensor 1
	}
}

NXP/Freesclae use method 2, not sure other vendors or history support
mehtod 1

Most system one port have only one endpoint, not sure previous design hope
endpont map to media pad or one port map to media pad.

It is the same if only one endpont under port. So far this version support
all devices, which use method 2.

Frank
>
> Many V4L2 subdev drivers implement the same registration and media pads.
> Assumes a 1:1 mapping between firmware endpoints and media pads.
> During registration it parses the firmware graph, creates media pads for
> all endpoints, and registers common asynchronous notifiers for sink
> endpoints. These notifiers automatically create media links when the
> corresponding remote source devices become available.
>
> The set_pad_by_ep() callback allows drivers to determine the media pad
> associated with a firmware endpoint and identify whether the endpoint
> represents a sink pad.
>
> By centralizing firmware graph parsing, media pad creation, notifier
> registration, and link creation, this helper reduces duplicated code and
> simplifies error handling in V4L2 sub-device drivers.
>
> Add media_async_register_subdev(), a helper to register a V4L2 sub-device
> with the asynchronous sub-device framework.
>
> This reduces code duplication and simplifies the implementation of
> simple bridge and converter drivers.
>
>     In subdev driver:
>
>     your_device_probe()
>     {
>             v4l2_subdev_init(sd, &dw_mipi_csi2rx_ops);
>             ...
>             return media_async_register_subdev_1to1(sd);
>     }
>
>     ...
>     your_device_remove()
>     {
>             media_async_subdev_cleanup(sd);
>     }
>
> This API help reduce over line duplcated code in synopsys/dw-mipi-csi2rx.c.
> And use this API at imx8's parallel CPI driver, which over 90% code now
> hardware related.
>
> And also benefit on going pix format patch
> https://lore.kernel.org/imx/20260525-csi_formatter-v8-0-6b646231224b@oss.nxp.com/
>
> It will also reduce missed media_entity_cleanup() problem at some error path
> https://lore.kernel.org/linux-media/20260614202835.11977-15-birenpandya@gmail.com/
>
> Previous do partial simpilfy at
> https://lore.kernel.org/imx/aaisdJSsFE5-PLx1@lizhi-Precision-Tower-5810/
>
> To: Sakari Ailus <sakari.ailus@linux.intel.com>
> To: Mauro Carvalho Chehab <mchehab@kernel.org>
> To: Michael Riesch <michael.riesch@collabora.com>
> To: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> To: Frank Li <Frank.Li@nxp.com>
> To: Martin Kepplinger-Novakovic <martink@posteo.de>
> To: Rui Miguel Silva <rmfrfs@gmail.com>
> To: Purism Kernel Team <kernel@puri.sm>
> To: Rob Herring <robh@kernel.org>
> To: Krzysztof Kozlowski <krzk+dt@kernel.org>
> To: Conor Dooley <conor+dt@kernel.org>
> To: Sascha Hauer <s.hauer@pengutronix.de>
> To: Pengutronix Kernel Team <kernel@pengutronix.de>
> To: Fabio Estevam <festevam@gmail.com>
> Cc: linux-media@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: imx@lists.linux.dev
> Cc: Guoniu Zhou <guoniu.zhou@nxp.com>
> Cc: devicetree@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> Changes in v6:
> - Change API to fix more width user case, assume a media pad have one endpoint
> on dts.
> - other detail change see each patch's change log
> - Link to v5: https://patch.msgid.link/20260617-imx8qxp_pcam-v5-0-7fa6c8e7fba7@nxp.com
>
> Changes in v5:
> - Add media_async_register_subdev_1to1() to simple code.
> - Link to v4: https://lore.kernel.org/r/20250729-imx8qxp_pcam-v4-0-4dfca4ed2f87@nxp.com
>
> Changes in v4:
> - remove imx93 driver support since have not camera sensor module to do test now.
>   Add it later
> - Add new patch
>   media: v4l2-common: Add helper function v4l_get_required_align_by_bpp()
> - See each patche's change log for detail.
> - Link to v3: https://lore.kernel.org/r/20250708-imx8qxp_pcam-v3-0-c8533e405df1@nxp.com
>
> Changes in v3:
> - replace CSI with CPI.
> - detail change see each patch's change logs
> - Link to v2: https://lore.kernel.org/r/20250703-imx8qxp_pcam-v2-0-188be85f06f1@nxp.com
>
> Changes in v2:
> - remove patch media: nxp: isi: add support for UYVY8_2X8 and YUYV8_2X8 bus codes
>   because pcif controller convert 2x8 to 1x16 to match isi's input
> - rename comaptible string to fsl,imx8qxp-pcif
> - See each patches's change log for detail
> - Link to v1: https://lore.kernel.org/r/20250630-imx8qxp_pcam-v1-0-eccd38d99201@nxp.com
>
> ---
> Alice Yuan (2):
>       dt-bindings: media: add i.MX parallel CPI support
>       media: nxp: add V4L2 subdev driver for camera parallel interface (CPI)
>
> Frank Li (7):
>       media: mc-entity: Store parsed V4L2 fwnode endpoint in media_pad
>       media: subdev: Add set_pad_by_ep() callback to internal ops
>       media: subdev: Add media_async_register_subdev() helper
>       media: synopsys: Use v4l2_subdev_get_frame_desc_passthrough()
>       media: synopsys: Use media_async_register_subdev() to simplify code
>       arm64: dts: imx8: add camera parallel interface (CPI) node
>       arm64: dts: imx8qxp-mek: add parallel ov5640 camera support
>
>  .../devicetree/bindings/media/fsl,imx93-pcif.yaml  | 126 +++++
>  MAINTAINERS                                        |   2 +
>  arch/arm64/boot/dts/freescale/Makefile             |   3 +
>  arch/arm64/boot/dts/freescale/imx8-ss-img.dtsi     |  13 +
>  .../boot/dts/freescale/imx8qxp-mek-ov5640-cpi.dtso |  83 +++
>  arch/arm64/boot/dts/freescale/imx8qxp-ss-img.dtsi  |  27 +
>  drivers/media/platform/nxp/Kconfig                 |  12 +
>  drivers/media/platform/nxp/Makefile                |   1 +
>  drivers/media/platform/nxp/imx-parallel-cpi.c      | 629 +++++++++++++++++++++
>  drivers/media/platform/synopsys/dw-mipi-csi2rx.c   | 200 ++-----
>  drivers/media/v4l2-core/v4l2-fwnode.c              | 155 +++++
>  include/media/media-entity.h                       |   5 +-
>  include/media/v4l2-async.h                         |  39 ++
>  include/media/v4l2-subdev.h                        |   5 +
>  14 files changed, 1140 insertions(+), 160 deletions(-)
> ---
> base-commit: c425f8be0326d40823cd93cbca633872d099df2a
> change-id: 20250626-imx8qxp_pcam-d851238343c3
>
> Best regards,
> --
> Frank Li <Frank.Li@nxp.com>
>
>

^ permalink raw reply

* Re: [RFC PATCH net-next v8 03/12] net: phylink: add phylink_release_pcs() to externally release a PCS
From: Maxime Chevallier @ 2026-06-25 14:13 UTC (permalink / raw)
  To: Christian Marangi, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Simon Horman, Jonathan Corbet, Shuah Khan,
	Lorenzo Bianconi, Heiner Kallweit, Russell King, Saravana Kannan,
	Philipp Zabel, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
	Justin Stitt, netdev, devicetree, linux-kernel, linux-doc,
	linux-arm-kernel, linux-mediatek, llvm
In-Reply-To: <20260618125752.1223-4-ansuelsmth@gmail.com>

Hello Christian,

On 6/18/26 14:57, Christian Marangi wrote:
> Add phylink_release_pcs() to externally release a PCS from a phylink
> instance. This can be used to handle case when a single PCS needs to be
> removed and the phylink instance needs to be refreshed.
> 
> On calling phylink_release_pcs(), the PCS will be removed from the
> phylink internal PCS list and the phylink supported_interfaces value is
> reparsed with the remaining PCS interfaces.
> 
> Also a phylink resolve is triggered to handle the PCS removal.
> 
> The flag force_major_config is set to make phylink resolve reconfigure
> the interface (even if it didn't change).
> This is needed to handle the special case when the current PCS used
> by phylink is removed and a major_config is needed to propagae the
> configuration change. With this option enabled we also force mac_config
> even if the PHY link is not up for the in-band case.
> 
> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> ---
>  drivers/net/phy/phylink.c | 56 +++++++++++++++++++++++++++++++++++++++
>  include/linux/phylink.h   |  2 ++
>  2 files changed, 58 insertions(+)
> 
> diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
> index c38bcd43b8c8..064d6f5a06da 100644
> --- a/drivers/net/phy/phylink.c
> +++ b/drivers/net/phy/phylink.c
> @@ -158,6 +158,8 @@ static const phy_interface_t phylink_sfp_interface_preference[] = {
>  static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);
>  
>  static void phylink_run_resolve(struct phylink *pl);
> +static void phylink_link_down(struct phylink *pl);
> +static void phylink_pcs_disable(struct phylink_pcs *pcs);
>  
>  /**
>   * phylink_set_port_modes() - set the port type modes in the ethtool mask
> @@ -918,6 +920,60 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state)
>  	}
>  }
>  
> +/**
> + * phylink_release_pcs - Removes a PCS from the phylink PCS available list
> + * @pcs: a pointer to the phylink_pcs struct to be released
> + *
> + * This function release a PCS from the phylink PCS available list if
> + * actually in use. It also refreshes the supported interfaces of the
> + * phylink instance by copying the supported interfaces from the phylink
> + * conf and merging the supported interfaces of the remaining available PCS
> + * in the list and trigger a resolve.
> + */
> +void phylink_release_pcs(struct phylink_pcs *pcs)
> +{
> +	struct phylink *pl;
> +
> +	ASSERT_RTNL();
> +
> +	pl = pcs->phylink;
> +	if (!pl)
> +		return;
> +
> +	mutex_lock(&pl->state_mutex);
> +
> +	list_del(&pcs->list);
> +	pcs->phylink = NULL;
> +
> +	/*
> +	 * Check if we are removing the PCS currently
> +	 * in use by phylink. If this is the case, tear down
> +	 * the link, force phylink resolve to reconfigure the
> +	 * interface mode, disable the current PCS and set the
> +	 * phylink PCS to NULL.
> +	 */
> +	if (pl->pcs == pcs) {
> +		phylink_link_down(pl);
> +		phylink_pcs_disable(pl->pcs);
> +
> +		pl->force_major_config = true;
> +		pl->pcs = NULL;
> +	}
> +
> +	mutex_unlock(&pl->state_mutex);
> +
> +	/* Refresh supported interfaces */
> +	phy_interface_copy(pl->supported_interfaces,
> +			   pl->config->supported_interfaces);
> +	list_for_each_entry(pcs, &pl->pcs_list, list)
> +		phy_interface_or(pl->supported_interfaces,
> +				 pl->supported_interfaces,
> +				 pcs->supported_interfaces);

I've given more thought to that 'supported_interfaces' thing. This
patchset redefines the meaning of

  pl->config->supported_interfaces

Currently, it's filled by the MAC driver and means "Every interface
we can support, including the ones provided by PCSs that we can use
with this MAC".

It now becomes "Every interface we support without needing a PCS", at
least the way I understand that.

It's not an error in your code, but I think this is worth documenting
somewhere as this changes one the things that's already fairly
error-prone in new drivers.

I don't know to what extent people use that, be we have a porting guide
that explains how to use phylink in a MAC driver, maybe an update in there
would be nice as well :

https://docs.kernel.org/networking/sfp-phylink.html#rough-guide-to-converting-a-network-driver-to-sfp-phylink

Maxime



^ permalink raw reply

* [PATCH 6/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
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>

Add nodes for the M0 remoteproc, reserved memory carveout, and Bluetooth
to bring up the M0 core and enable the Bluetooth Subsystem.

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 arch/arm64/boot/dts/qcom/ipq5018.dtsi | 34 +++++++++++++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/qcom/ipq5018.dtsi b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
index 6f8004a22a1f..4fdf20c87b0a 100644
--- a/arch/arm64/boot/dts/qcom/ipq5018.dtsi
+++ b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
@@ -17,6 +17,17 @@ / {
 	#address-cells = <2>;
 	#size-cells = <2>;
 
+	bluetooth: bluetooth {
+		compatible = "qcom,ipq5018-bt";
+
+		qcom,ipc = <&apcs_glb 8 23>;
+		interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+		qcom,rproc = <&m0_btss>;
+
+		status = "disabled";
+	};
+
 	clocks {
 		gephy_rx_clk: gephy-rx-clk {
 			compatible = "fixed-clock";
@@ -131,11 +142,31 @@ psci {
 		method = "smc";
 	};
 
+	m0_btss: 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>;
+
+		status = "disabled";
+	};
+
 	reserved-memory {
 		#address-cells = <2>;
 		#size-cells = <2>;
 		ranges;
 
+		btss_region: bluetooth@7000000 {
+			reg = <0x0 0x07000000 0x0 0x58000>;
+			no-map;
+		};
+
 		bootloader@4a800000 {
 			reg = <0x0 0x4a800000 0x0 0x200000>;
 			no-map;
@@ -647,7 +678,8 @@ watchdog: watchdog@b017000 {
 
 		apcs_glb: mailbox@b111000 {
 			compatible = "qcom,ipq5018-apcs-apps-global",
-				     "qcom,ipq6018-apcs-apps-global";
+				     "qcom,ipq6018-apcs-apps-global",
+				     "syscon";
 			reg = <0x0b111000 0x1000>;
 			#clock-cells = <1>;
 			clocks = <&a53pll>, <&xo_board_clk>, <&gcc GPLL0>;

-- 
2.53.0



^ permalink raw reply related

* [PATCH 5/6] Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
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>

Add driver support for the Qualcomm IPQ5018 bluetooth chip.
The firmware runs on the M0 co-processor.

The host and the M0 core use a shared memory carveout for transport
using ring buffers. This driver implements the transport layer between
the HCI core and the Bluetooth subsystem running on the M0 core.

Notifications of host and M0 core events are triggered by an IPC
register BIT and an interrupt line respectfully.

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 drivers/bluetooth/Kconfig     |  11 +
 drivers/bluetooth/Makefile    |   1 +
 drivers/bluetooth/btqcomipc.c | 939 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 951 insertions(+)

diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 4e8c24d757e9..6b8bed6a6ffd 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -413,6 +413,17 @@ config BT_MTKUART
 	  Say Y here to compile support for MediaTek Bluetooth UART devices
 	  into the kernel or say M to compile it as module (btmtkuart).
 
+config BT_QCOMIPC
+	tristate "Qualcomm IPQ5018 IPC based HCI support"
+	select BT_QCA
+	help
+	  Qualcomm IPQ5018 IPC based HCI driver.
+	  This driver is used to bridge HCI data onto shared memory between
+	  the host and the M0 BTSS core.
+
+	  Say Y here to compile support for HCI over Qualcomm IPC into the
+	  kernel or say M to compile as a module.
+
 config BT_QCOMSMD
 	tristate "Qualcomm SMD based HCI support"
 	depends on RPMSG || (COMPILE_TEST && RPMSG=n)
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index e6b1c1180d1d..05f19047bed0 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_BT_MRVL)		+= btmrvl.o
 obj-$(CONFIG_BT_MRVL_SDIO)	+= btmrvl_sdio.o
 obj-$(CONFIG_BT_MTKSDIO)	+= btmtksdio.o
 obj-$(CONFIG_BT_MTKUART)	+= btmtkuart.o
+obj-$(CONFIG_BT_QCOMIPC)	+= btqcomipc.o
 obj-$(CONFIG_BT_QCOMSMD)	+= btqcomsmd.o
 obj-$(CONFIG_BT_BCM)		+= btbcm.o
 obj-$(CONFIG_BT_RTL)		+= btrtl.o
diff --git a/drivers/bluetooth/btqcomipc.c b/drivers/bluetooth/btqcomipc.c
new file mode 100644
index 000000000000..662a75b6c4a9
--- /dev/null
+++ b/drivers/bluetooth/btqcomipc.c
@@ -0,0 +1,939 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/remoteproc.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "btqca.h"
+
+/** Message header format.
+ *
+ *        ----------------------------------------------------------------
+ * BitPos |    15    | 14 | 13 | 12 | 11 | 10 |  9  |  8  |    7 - 0     |
+ *         ---------------------------------------------------------------
+ * Field  | long_msg |ACK |        RFU        |  pkt_type |    cmd       |
+ *        ----------------------------------------------------------------
+ *
+ * - long_msg   : If set, indicates that the payload is larger than the
+ *                IPC_MSG_PLD_SZ. The payload instead contains a pointer to the
+ *                long message buffer in the shared BTSS memory space.
+ *
+ * - ACK       : Set if sending ACK if required by sending acknowledegement
+ *                to sender i.e. send an ack IPC interrupt if set.
+ *
+ * - RFU        : Reserved for future use.
+ *
+ * - pkt_type   : IPC Packet Type
+ *
+ * - cmd        : Contains unique command ID
+ */
+
+#define IPC_MSG_HDR_SZ		4
+#define IPC_MSG_PLD_SZ		40
+#define IPC_TOTAL_MSG_SZ	(IPC_MSG_HDR_SZ + IPC_MSG_PLD_SZ)
+
+/* Message Header */
+#define IPC_HDR_LONG_MSG	BIT(15)
+#define IPC_HDR_REQ_ACK		BIT(14)
+#define IPC_HDR_PKT_TYPE_MASK	GENMASK(9, 8)
+#define  IPC_HDR_PKT_TYPE_CUST	0
+#define  IPC_HDR_PKT_TYPE_HCI	1
+#define  IPC_HDR_PKT_TYPE_AUDIO	2
+#define  IPC_HDR_PKT_TYPE_RFU	3
+#define IPC_HDR_CMD_MASK	GENMASK(7, 0)
+
+#define IPC_CMD_STOP		1
+#define IPC_CMD_SWITCH_TO_UART	2
+#define IPC_CMD_PREPARE_DUMP	3
+#define IPC_CMD_COLLECT_DUMP	4
+#define IPC_CMD_START		5
+
+#define IPC_TX_QSIZE		32
+
+#define	TO_APPS_ADDR(a)		(desc->mem_region + (int)(uintptr_t)a)
+#define	TO_BT_ADDR(a)		(a - desc->mem_region)
+#define IPC_LBUF_SZ(w, x, y, z)	(((TO_BT_ADDR((void *)w) + w->x) - w->y) / w->z)
+
+#define	GET_NO_OF_BLOCKS(a, b) ((a + b - 1) / b)
+
+#define GET_RX_INDEX_FROM_BUF(x, y)	((x - desc->rx_ctxt->lring_buf) / y)
+
+#define GET_TX_INDEX_FROM_BUF(x, y)	((x - desc->tx_ctxt->lring_buf) / y)
+
+#define IS_RX_MEM_NON_CONTIGIOUS(buf, len, sz)		\
+	((buf + len) > (desc->rx_ctxt->lring_buf +	\
+	(sz * desc->rx_ctxt->lmsg_buf_cnt)))
+
+#define POWER_CONTROL_DELAY_MS	50
+
+#define BTSS_PAS_ID	0xc
+
+struct long_msg_info {
+	__le16 smsg_free_cnt;
+	__le16 lmsg_free_cnt;
+	u8 ridx;
+	u8 widx;
+} __packed;
+
+struct ipc_aux_ptr {
+	__le32 len;
+	__le32 buf;
+} __packed;
+
+struct ring_buffer {
+	__le16 msg_hdr;
+	__le16 len;
+	union {
+		u8 smsg_data[IPC_MSG_PLD_SZ];
+		__le32 lmsg_data;
+	} payload;
+} __packed;
+
+struct ring_buffer_info {
+	__le32 rbuf;
+	u8 ring_buf_cnt;
+	u8 ridx;
+	u8 widx;
+	u8 tidx;
+	__le32 next;
+} __packed;
+
+struct context_info {
+	__le16 total_size;
+	u8 lmsg_buf_cnt;
+	u8 smsg_buf_cnt;
+	struct ring_buffer_info sring_buf_info;
+	__le32 sring_buf;
+	__le32 lring_buf;
+	__le32 reserved;
+} __packed;
+
+struct qcom_btss {
+	struct device *dev;
+	struct rproc *rproc;
+	struct hci_dev *hdev;
+
+	struct regmap *regmap;
+	u32 offset;
+	u32 bit;
+	int irq;
+
+	void *mem_region;
+	phys_addr_t mem_phys;
+	phys_addr_t mem_reloc;
+	size_t mem_size;
+
+	struct sk_buff_head tx_q;
+	struct workqueue_struct *wq;
+	struct work_struct work;
+	wait_queue_head_t wait_q;
+	spinlock_t lock;
+
+	struct context_info *tx_ctxt;
+	struct context_info *rx_ctxt;
+	struct long_msg_info lmsg_ctxt;
+
+	bool running;
+};
+
+static void btqcomipc_update_stats(struct hci_dev *hdev, struct sk_buff *skb);
+
+static void *btss_alloc_lmsg(struct qcom_btss *desc, u32 len,
+			     struct ipc_aux_ptr *aux_ptr, bool *is_lbuf_full)
+{
+	struct device *dev = desc->dev;
+	u8 idx, blks, blks_consumed;
+	void *ret_ptr;
+	u32 lsz;
+
+	if (desc->tx_ctxt->lring_buf == 0) {
+		dev_err(dev, "no long message buffer initialized\n");
+		return ERR_PTR(-ENODEV);
+	}
+
+	lsz = IPC_LBUF_SZ(desc->tx_ctxt, total_size, lring_buf, lmsg_buf_cnt);
+	blks = GET_NO_OF_BLOCKS(len, lsz);
+
+	if (!desc->lmsg_ctxt.lmsg_free_cnt ||
+			(blks > desc->lmsg_ctxt.lmsg_free_cnt))
+		return ERR_PTR(-EAGAIN);
+
+	idx = desc->lmsg_ctxt.widx;
+
+	if ((desc->lmsg_ctxt.widx + blks) > desc->tx_ctxt->lmsg_buf_cnt) {
+		blks_consumed = desc->tx_ctxt->lmsg_buf_cnt - idx;
+		aux_ptr->len = len - (blks_consumed * lsz);
+		aux_ptr->buf = desc->tx_ctxt->lring_buf;
+	}
+
+	desc->lmsg_ctxt.widx = (desc->lmsg_ctxt.widx + blks) %
+		desc->tx_ctxt->lmsg_buf_cnt;
+
+	desc->lmsg_ctxt.lmsg_free_cnt -= blks;
+
+	if (desc->lmsg_ctxt.lmsg_free_cnt <=
+			((desc->tx_ctxt->lmsg_buf_cnt * 20) / 100))
+		*is_lbuf_full = true;
+
+	ret_ptr = TO_APPS_ADDR(desc->tx_ctxt->lring_buf) + (idx * lsz);
+
+	return ret_ptr;
+}
+
+static struct ring_buffer_info *btss_get_tx_rbuf(struct qcom_btss *desc,
+						 bool *is_sbuf_full)
+{
+	u8 idx;
+	struct ring_buffer_info *rinfo;
+
+	for (rinfo = &(desc->tx_ctxt->sring_buf_info);	rinfo != NULL;
+		rinfo = (struct ring_buffer_info *)(uintptr_t)(rinfo->next)) {
+		idx = (rinfo->widx + 1) % (desc->tx_ctxt->smsg_buf_cnt);
+
+		if (idx != rinfo->tidx) {
+			desc->lmsg_ctxt.smsg_free_cnt--;
+
+			if (desc->lmsg_ctxt.smsg_free_cnt <=
+				((desc->tx_ctxt->smsg_buf_cnt * 20) / 100))
+				*is_sbuf_full = true;
+
+			return rinfo;
+		}
+	}
+
+	return ERR_PTR(-EAGAIN);
+}
+
+static int btss_send(struct qcom_btss *desc, u16 msg_hdr,
+		     struct sk_buff *skb)
+{
+	struct hci_dev *hdev = desc->hdev;
+	struct ring_buffer_info *rinfo;
+	struct ipc_aux_ptr aux_ptr;
+	struct ring_buffer *rbuf;
+	bool is_lbuf_full = false;
+	bool is_sbuf_full = false;
+	u16 hdr = msg_hdr;
+	void *ptr_buf;
+	u32 len;
+
+	/* Account for HCI packet type as it's not included in the skb payload */
+	len = skb->len + 1;
+	memset(&aux_ptr, 0, sizeof(struct ipc_aux_ptr));
+
+	if (len > IPC_MSG_PLD_SZ) {
+		hdr |= IPC_HDR_LONG_MSG;
+
+		ptr_buf = btss_alloc_lmsg(desc, len,
+					  &aux_ptr, &is_lbuf_full);
+		if (IS_ERR(ptr_buf)) {
+			bt_dev_err(hdev, "long msg buf full");
+			hdev->stat.err_tx++;
+			return PTR_ERR(ptr_buf);
+		}
+	}
+
+	rinfo = btss_get_tx_rbuf(desc, &is_sbuf_full);
+	if (IS_ERR(rinfo)) {
+		bt_dev_err(hdev, "short msg buf full");
+		hdev->stat.err_tx++;
+		return PTR_ERR(rinfo);
+	}
+
+	rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(rinfo->rbuf)))[rinfo->widx];
+
+	if (len > IPC_MSG_PLD_SZ)
+		rbuf->payload.lmsg_data = cpu_to_le32(TO_BT_ADDR(ptr_buf));
+	else
+		ptr_buf = rbuf->payload.smsg_data;
+
+	/* if it's a short message, the aux len and buf are NULL */
+	memcpy_toio(ptr_buf, &hci_skb_pkt_type(skb), 1);
+	memcpy_toio((u8 *)ptr_buf + 1, skb->data, skb->len - aux_ptr.len);
+	if (aux_ptr.buf) {
+		memcpy_toio(TO_APPS_ADDR(aux_ptr.buf),
+			    (skb->data + (skb->len - aux_ptr.len)), aux_ptr.len);
+	}
+
+	if (is_sbuf_full || is_lbuf_full)
+		hdr |= IPC_HDR_REQ_ACK;
+
+	rbuf->msg_hdr = cpu_to_le16(hdr);
+	rbuf->len = cpu_to_le16(len);
+
+	rinfo->widx = (rinfo->widx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+
+	regmap_set_bits(desc->regmap, desc->offset, BIT(desc->bit));
+
+	return 0;
+}
+
+static void btss_process_tx_queue(struct qcom_btss *desc)
+{
+	struct sk_buff *skb;
+	u16 hdr;
+	int ret;
+
+	while ((skb = skb_dequeue(&desc->tx_q))) {
+		hdr = FIELD_PREP(IPC_HDR_PKT_TYPE_MASK, IPC_HDR_PKT_TYPE_HCI);
+
+		ret = btss_send(desc, hdr, skb);
+		if (ret) {
+			bt_dev_err(desc->hdev, "Failed to send message");
+			skb_queue_head(&desc->tx_q, skb);
+			break;
+		}
+
+		btqcomipc_update_stats(desc->hdev, skb);
+		kfree_skb(skb);
+	}
+}
+
+static void btss_free_lmsg(struct qcom_btss *desc, u32 lmsg, u16 len)
+{
+	u8 idx;
+	u8 blks;
+	u32 lsz = IPC_LBUF_SZ(desc->tx_ctxt, total_size, lring_buf,
+				   lmsg_buf_cnt);
+
+	idx = GET_TX_INDEX_FROM_BUF(lmsg, lsz);
+
+	if (idx != desc->lmsg_ctxt.ridx)
+		return;
+
+	blks = GET_NO_OF_BLOCKS(len, lsz);
+
+	desc->lmsg_ctxt.ridx  = (desc->lmsg_ctxt.ridx  + blks) %
+		desc->tx_ctxt->lmsg_buf_cnt;
+
+	desc->lmsg_ctxt.lmsg_free_cnt += blks;
+}
+
+static int btss_send_ctrl(struct qcom_btss *desc, u16 msg_hdr)
+{
+	struct ring_buffer_info *rinfo;
+	struct ring_buffer *rbuf;
+
+	rinfo = btss_get_tx_rbuf(desc, NULL);
+	if (IS_ERR(rinfo))
+		return PTR_ERR(rinfo);
+
+	rbuf = &((struct ring_buffer *)TO_APPS_ADDR(rinfo->rbuf))[rinfo->widx];
+	rbuf->msg_hdr = cpu_to_le16(msg_hdr);
+	rbuf->len = 0;
+
+	rinfo->widx = (rinfo->widx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+
+	regmap_set_bits(desc->regmap, desc->offset, BIT(desc->bit));
+
+	return 0;
+}
+
+static void btss_recv_cust_frame(struct qcom_btss *desc, u8 cmd)
+{
+	u16 msg_hdr = 0;
+	int ret;
+
+	msg_hdr |= cmd;
+
+	switch (cmd) {
+	case IPC_CMD_STOP:
+		bt_dev_info(desc->hdev, "BTSS stopped, gracefully stopping APSS IPC");
+		break;
+	case IPC_CMD_START:
+		desc->tx_ctxt = (struct context_info *)((void *)desc->rx_ctxt +
+				le16_to_cpu(desc->rx_ctxt->total_size));
+		desc->lmsg_ctxt.widx = 0;
+		desc->lmsg_ctxt.ridx = 0;
+		desc->lmsg_ctxt.smsg_free_cnt = desc->tx_ctxt->smsg_buf_cnt;
+		desc->lmsg_ctxt.lmsg_free_cnt = desc->tx_ctxt->lmsg_buf_cnt;
+		WRITE_ONCE(desc->running, true);
+		wake_up(&desc->wait_q);
+
+		bt_dev_info(desc->hdev, "BTSS started, initializing APSS BT IPC");
+		return;
+	default:
+		bt_dev_err(desc->hdev, "Unsupported CMD ID: %u", cmd);
+		return;
+	}
+
+	if (unlikely(!READ_ONCE(desc->running))) {
+		bt_dev_err(desc->hdev, "BTSS not initialized, no message sent");
+		return;
+	}
+
+	WRITE_ONCE(desc->running, false);
+
+	ret = btss_send_ctrl(desc, msg_hdr);
+	if (ret)
+		bt_dev_err(desc->hdev, "Failed to send control message");
+}
+
+static inline int btss_recv_hci_frame(struct qcom_btss *desc, const u8 *data, size_t len)
+{
+	unsigned char pkt_type;
+	struct sk_buff *skb;
+	size_t pkt_len;
+
+	if (len < 1)
+		return -EPROTO;
+
+	pkt_type = data[0];
+
+	switch (pkt_type) {
+	case HCI_EVENT_PKT:
+	{
+		if (len < HCI_EVENT_HDR_SIZE)
+			return -EILSEQ;
+		struct hci_event_hdr *hdr = (struct hci_event_hdr *)(data + 1);
+
+		pkt_len = HCI_EVENT_HDR_SIZE + hdr->plen;
+		break;
+	}
+	case HCI_COMMAND_PKT: {
+		if (len < HCI_COMMAND_HDR_SIZE)
+			return -EILSEQ;
+		struct hci_command_hdr *hdr = (struct hci_command_hdr *)(data + 1);
+
+		pkt_len = HCI_COMMAND_HDR_SIZE + le16_to_cpu(hdr->plen);
+		break;
+	}
+	case HCI_ACLDATA_PKT:
+	{
+		if (len < HCI_ACL_HDR_SIZE)
+			return -EILSEQ;
+		struct hci_acl_hdr *hdr = (struct hci_acl_hdr *)(data + 1);
+
+		pkt_len = HCI_ACL_HDR_SIZE + le16_to_cpu(hdr->dlen);
+		break;
+	}
+	case HCI_SCODATA_PKT:
+	{
+		if (len < HCI_SCO_HDR_SIZE)
+			return -EILSEQ;
+		struct hci_sco_hdr *hdr = (struct hci_sco_hdr *)(data + 1);
+
+		pkt_len = HCI_SCO_HDR_SIZE + hdr->dlen;
+		break;
+	}
+	default:
+		return -EPROTO;
+	}
+
+	if (pkt_len > len)
+		return -EINVAL;
+
+	skb = bt_skb_alloc(pkt_len, GFP_ATOMIC);
+	if (!skb) {
+		desc->hdev->stat.err_rx++;
+		return -ENOMEM;
+	}
+
+	skb->dev = (void *)desc->hdev;
+	hci_skb_pkt_type(skb) = pkt_type;
+	skb_put_data(skb, data + 1, pkt_len);
+
+	hci_recv_frame(desc->hdev, skb);
+	desc->hdev->stat.byte_rx += pkt_len;
+
+	return 0;
+}
+
+static inline int btss_process_rx(struct qcom_btss *desc,
+				  struct ring_buffer_info *rinfo,
+				  bool *ack, u8 *rx_count)
+{
+	u8 ridx, lbuf_idx, blks_consumed, pkt_type, cmd;
+	struct ipc_aux_ptr aux_ptr;
+	struct ring_buffer *rbuf;
+	uint8_t *rxbuf = NULL;
+	unsigned char *buf;
+	u32 lsz;
+	int ret;
+
+	ridx = rinfo->ridx;
+
+	while (ridx != rinfo->widx) {
+		memset(&aux_ptr, 0, sizeof(struct ipc_aux_ptr));
+
+		rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(rinfo->rbuf)))[ridx];
+
+		if (rbuf->msg_hdr & IPC_HDR_LONG_MSG) {
+			rxbuf = TO_APPS_ADDR(rbuf->payload.lmsg_data);
+			lsz = IPC_LBUF_SZ(desc->rx_ctxt, total_size, lring_buf,
+				   lmsg_buf_cnt);
+
+			if (IS_RX_MEM_NON_CONTIGIOUS(rbuf->payload.lmsg_data,
+						     rbuf->len, lsz)) {
+				lbuf_idx = GET_RX_INDEX_FROM_BUF(
+						rbuf->payload.lmsg_data, lsz);
+
+				blks_consumed = desc->rx_ctxt->lmsg_buf_cnt -
+					lbuf_idx;
+				aux_ptr.len = rbuf->len - (blks_consumed * lsz);
+				aux_ptr.buf = desc->rx_ctxt->lring_buf;
+			}
+		} else {
+			rxbuf = rbuf->payload.smsg_data;
+		}
+
+		*ack = (rbuf->msg_hdr & IPC_HDR_REQ_ACK);
+
+		pkt_type = FIELD_GET(IPC_HDR_PKT_TYPE_MASK, rbuf->msg_hdr);
+
+		switch (pkt_type) {
+		case IPC_HDR_PKT_TYPE_HCI:
+			buf = kmalloc(rbuf->len, GFP_ATOMIC);
+			if (!buf) {
+				rinfo->ridx = ridx;
+				return -ENOMEM;
+			}
+
+			memcpy_fromio(buf, rxbuf, rbuf->len - aux_ptr.len);
+
+			if (aux_ptr.buf)
+				memcpy_fromio(buf + (rbuf->len - aux_ptr.len),
+					      TO_APPS_ADDR(aux_ptr.buf), aux_ptr.len);
+
+			ret = btss_recv_hci_frame(desc, buf, rbuf->len);
+			if (ret)
+				bt_dev_err(desc->hdev, "Failed to process HCI frame: %d", ret);
+			kfree(buf);
+			break;
+		case IPC_HDR_PKT_TYPE_CUST:
+			cmd = FIELD_GET(IPC_HDR_CMD_MASK, rbuf->msg_hdr);
+			btss_recv_cust_frame(desc, cmd);
+			break;
+		default:
+			break;
+		}
+
+		ridx = (ridx + 1) % rinfo->ring_buf_cnt;
+
+		if (rx_count)
+			(*rx_count)++;
+
+		rinfo->ridx = ridx;
+	}
+
+	return 0;
+}
+
+static void btss_process_ack(struct qcom_btss *desc)
+{
+	struct ring_buffer_info *rinfo;
+	struct ring_buffer *rbuf;
+	u8 tidx;
+
+	for (rinfo = &desc->tx_ctxt->sring_buf_info; rinfo != NULL;
+		rinfo = (struct ring_buffer_info *)(uintptr_t)(rinfo->next)) {
+		tidx = rinfo->tidx;
+		rbuf = (struct ring_buffer *)TO_APPS_ADDR(rinfo->rbuf);
+
+		while (tidx != rinfo->ridx) {
+			if (rbuf[tidx].msg_hdr & IPC_HDR_LONG_MSG) {
+				btss_free_lmsg(desc,
+					       rbuf[tidx].payload.lmsg_data,
+					       rbuf[tidx].len);
+			}
+
+			tidx = (tidx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+			desc->lmsg_ctxt.smsg_free_cnt++;
+		}
+
+		rinfo->tidx = tidx;
+
+		btss_process_tx_queue(desc);
+	}
+}
+
+static void btss_worker(struct work_struct *work)
+{
+	struct qcom_btss *desc = container_of(work, struct qcom_btss, work);
+	struct ring_buffer_info *rinfo;
+	bool ack = false;
+	u32 offset;
+	int ret;
+
+	if (desc->rproc->state != RPROC_RUNNING)
+		return;
+
+	spin_lock(&desc->lock);
+
+	if (unlikely(!READ_ONCE(desc->running))) {
+		// FW sets offset of RX context info at start of memory region upon boot
+		offset = readl(desc->mem_region);
+		dev_dbg(desc->dev, "offset after M0 boot: 0x%08x\n", offset);
+		desc->rx_ctxt = (struct context_info *)(desc->mem_region + offset);
+	} else {
+		btss_process_ack(desc);
+	}
+
+	for (rinfo = &(desc->rx_ctxt->sring_buf_info);
+	     rinfo != NULL;
+	     rinfo = (struct ring_buffer_info *)(uintptr_t)(rinfo->next)) {
+		ret = btss_process_rx(desc, rinfo, &ack,
+				      &desc->rx_ctxt->smsg_buf_cnt);
+		if (ret) {
+			bt_dev_err(desc->hdev, "Failed to process peer msgs: %d", ret);
+			goto spin_unlock;
+		}
+	}
+
+	if (ack)
+		regmap_set_bits(desc->regmap, desc->offset, BIT(desc->bit));
+
+spin_unlock:
+	spin_unlock(&desc->lock);
+}
+
+static irqreturn_t btss_irq_handler(int irq, void *data)
+{
+	struct qcom_btss *desc = data;
+
+	queue_work(desc->wq, &desc->work);
+
+	return IRQ_HANDLED;
+}
+
+static int btss_init(struct qcom_btss *desc)
+{
+	struct device *dev = desc->dev;
+	int ret;
+
+	init_waitqueue_head(&desc->wait_q);
+	spin_lock_init(&desc->lock);
+	skb_queue_head_init(&desc->tx_q);
+
+	desc->wq = create_singlethread_workqueue("btss_wq");
+	if (!desc->wq) {
+		dev_err(dev, "Failed to initialize workqueue\n");
+		return -EAGAIN;
+	}
+
+	INIT_WORK(&desc->work, btss_worker);
+
+	ret = devm_request_threaded_irq(dev, desc->irq, NULL, btss_irq_handler,
+					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+					"btss_irq", desc);
+
+	if (ret)
+		dev_err(dev, "error registering irq[%d] ret = %d\n",
+			desc->irq, ret);
+
+	return ret;
+}
+
+static void btqcomipc_update_stats(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	u8 pkt_type = hci_skb_pkt_type(skb);
+
+	hdev->stat.byte_tx += skb->len;
+	switch (pkt_type) {
+	case HCI_COMMAND_PKT:
+		hdev->stat.cmd_tx++;
+		break;
+	case HCI_ACLDATA_PKT:
+		hdev->stat.acl_tx++;
+		break;
+	case HCI_SCODATA_PKT:
+		hdev->stat.sco_tx++;
+		break;
+	default:
+		break;
+	}
+}
+
+static int btqcomipc_open(struct hci_dev *hdev)
+{
+	struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+	int ret;
+
+	ret = btss_init(desc);
+	if (ret) {
+		bt_dev_err(hdev, "Failed initializing BTSS: %d", ret);
+		return ret;
+	}
+
+	/* wait 2 seconds for filesystem to become available */
+	msleep(2000);
+
+	/* Boot M0 firmware */
+	ret = rproc_boot(desc->rproc);
+	if (ret) {
+		bt_dev_err(hdev, "Failed to boot M0 processor: %d", ret);
+		return ret;
+	}
+
+	msleep(POWER_CONTROL_DELAY_MS);
+	ret = wait_event_timeout(desc->wait_q, READ_ONCE(desc->running),
+				 msecs_to_jiffies(1000));
+
+	if (!ret) {
+		bt_dev_err(hdev, "Timeout waiting for BTSS start");
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int btqcomipc_close(struct hci_dev *hdev)
+{
+	struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+	rproc_shutdown(desc->rproc);
+	msleep(POWER_CONTROL_DELAY_MS);
+
+	return 0;
+}
+
+static int btqcomipc_shutdown(struct hci_dev *hdev)
+{
+	struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+	WRITE_ONCE(desc->running, false);
+	if (desc->wq != NULL) {
+		disable_irq(desc->irq);
+		flush_workqueue(desc->wq);
+		devm_free_irq(desc->dev, desc->irq, desc);
+		skb_queue_purge(&desc->tx_q);
+		destroy_workqueue(desc->wq);
+		desc->wq = NULL;
+	}
+
+	return 0;
+}
+
+static int btqcomipc_setup(struct hci_dev *hdev)
+{
+	struct qca_btsoc_version ver;
+	int ret;
+
+	/*
+	 * Enable controller to do both LE scan and BR/EDR inquiry
+	 * simultaneously.
+	 */
+	hci_set_quirk(hdev, HCI_QUIRK_SIMULTANEOUS_DISCOVERY);
+
+	/*
+	 * Enable NON_PERSISTENT_SETUP QUIRK to ensure to execute
+	 * setup for every hci up.
+	 */
+	hci_set_quirk(hdev, HCI_QUIRK_NON_PERSISTENT_SETUP);
+	ret = qca_read_soc_version(hdev, &ver, QCA_IPQ5018);
+	if (ret)
+		return -EINVAL;
+
+	ret = qca_uart_setup(hdev, 0, QCA_IPQ5018, ver, NULL);
+	if (ret) {
+		bt_dev_err(hdev, "Failed to setup UART: %d\n", ret);
+		return ret;
+	}
+
+	bt_dev_info(hdev, "QCA Build Info: %s", hdev->fw_info);
+
+	/* Obtain and set BD address from NVMEM cell */
+	hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM);
+	hci_set_quirk(hdev, HCI_QUIRK_BDADDR_NVMEM_BE);
+
+	return 0;
+}
+
+static int btqcomipc_send(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	u16 hdr = FIELD_PREP(IPC_HDR_PKT_TYPE_MASK, IPC_HDR_PKT_TYPE_HCI);
+	struct qcom_btss *desc = hci_get_drvdata(hdev);
+	int ret;
+
+	if (unlikely(!READ_ONCE(desc->running))) {
+		bt_dev_err(hdev, "BTSS not initialized, failed to send message");
+		ret = -ENODEV;
+		goto free_skb;
+	}
+
+	ret = btss_send(desc, hdr, skb);
+	if (ret) {
+		if (ret == -EAGAIN) {
+			if (skb_queue_len(&desc->tx_q) >= IPC_TX_QSIZE) {
+				bt_dev_err(hdev, "TX queue full, dropping message");
+				hdev->stat.err_tx++;
+				ret = -ENOBUFS;
+			} else {
+				skb_queue_tail(&desc->tx_q, skb);
+				return 0;
+			}
+		} else {
+			bt_dev_err(hdev, "Failed to send message: %d", ret);
+			hdev->stat.err_tx++;
+		}
+	}
+
+	btqcomipc_update_stats(desc->hdev, skb);
+
+free_skb:
+	kfree_skb(skb);
+
+	return ret;
+}
+
+static int btqcomipc_flush(struct hci_dev *hdev)
+{
+	struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+	skb_queue_purge(&desc->tx_q);
+	return 0;
+}
+
+static int btqcomipc_alloc_memory_region(struct qcom_btss *desc)
+{
+	struct device *dev = desc->dev;
+	struct resource res;
+	int ret;
+
+	/* lookup reserved-memory region of the remoteproc node */
+	ret = of_reserved_mem_region_to_resource(desc->rproc->dev.parent->of_node, 0, &res);
+	if (ret) {
+		dev_err(dev, "unable to acquire memory-region resource\n");
+		return ret;
+	}
+
+	desc->mem_phys = res.start;
+	desc->mem_reloc = res.start;
+	desc->mem_size = resource_size(&res);
+	desc->mem_region = devm_ioremap(dev, desc->mem_phys, desc->mem_size);
+	if (!desc->mem_region) {
+		dev_err(dev, "unable to map memory region: %pR\n", &res);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void btqcomipc_rproc_put(void *data)
+{
+	struct rproc *rproc = data;
+
+	rproc_put(rproc);
+}
+
+static int btqcomipc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct qcom_btss *desc;
+	phandle rproc_phandle;
+	struct hci_dev *hdev;
+	unsigned int args[2];
+	int ret;
+
+	desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+	if (!desc)
+		return -ENOMEM;
+
+	desc->dev = dev;
+
+	if (of_property_read_u32(dev->of_node, "qcom,rproc", &rproc_phandle))
+		return dev_err_probe(dev, -ENOENT, "Failed to get remoteproc handle\n");
+
+	desc->rproc = rproc_get_by_phandle(rproc_phandle);
+	if (!desc->rproc)
+		return dev_err_probe(dev, -EPROBE_DEFER, "Failed to get remoteproc\n");
+
+	devm_add_action_or_reset(dev, btqcomipc_rproc_put, desc->rproc);
+
+	ret = btqcomipc_alloc_memory_region(desc);
+	if (ret)
+		return ret;
+
+	desc->regmap = syscon_regmap_lookup_by_phandle_args(dev->of_node, "qcom,ipc", 2, args);
+	if (IS_ERR(desc->regmap))
+		return PTR_ERR(desc->regmap);
+
+	desc->offset = args[0];
+	desc->bit = args[1];
+
+	desc->irq = platform_get_irq(pdev, 0);
+	if (desc->irq < 0)
+		return dev_err_probe(dev, desc->irq, "Failed to acquire IRQ\n");
+
+	hdev = hci_alloc_dev();
+	if (!hdev)
+		return -ENOMEM;
+
+	hci_set_drvdata(hdev, desc);
+	desc->hdev = hdev;
+	SET_HCIDEV_DEV(hdev, &pdev->dev);
+	hdev->bus = HCI_IPC;
+
+	hdev->open = btqcomipc_open;
+	hdev->close = btqcomipc_close;
+	hdev->shutdown = btqcomipc_shutdown;
+	hdev->setup = btqcomipc_setup;
+	hdev->send = btqcomipc_send;
+	hdev->flush = btqcomipc_flush;
+	hdev->set_bdaddr = qca_set_bdaddr;
+
+	ret = hci_register_dev(hdev);
+	if (ret < 0) {
+		hci_free_dev(hdev);
+		return dev_err_probe(dev, -EBUSY, "Failed to register hdev\n");
+	}
+
+	platform_set_drvdata(pdev, desc);
+
+	return 0;
+}
+
+static void btqcomipc_remove(struct platform_device *pdev)
+{
+	struct qcom_btss *desc = platform_get_drvdata(pdev);
+
+	if (!desc || !desc->hdev)
+		return;
+
+	hci_unregister_dev(desc->hdev);
+	hci_free_dev(desc->hdev);
+}
+
+static const struct of_device_id btqcomipc_of_match[] = {
+	{ .compatible = "qcom,ipq5018-bt" },
+	{ /* sentinel */},
+};
+MODULE_DEVICE_TABLE(of, btqcomipc_of_match);
+
+static struct platform_driver btqcomipc_driver = {
+	.probe = btqcomipc_probe,
+	.remove = btqcomipc_remove,
+	.driver = {
+		.name = "btqcomipc",
+		.of_match_table = btqcomipc_of_match,
+	},
+};
+
+module_platform_driver(btqcomipc_driver);
+
+MODULE_DESCRIPTION("Qualcomm Bluetooth IPC Driver");
+MODULE_LICENSE("GPL");

-- 
2.53.0



^ permalink raw reply related

* [PATCH 4/6] dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
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 Qualcomm IPQ5018 Bluetooth controller.

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 .../bindings/net/bluetooth/qcom,ipq5018-bt.yaml    | 63 ++++++++++++++++++++++
 1 file changed, 63 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
new file mode 100644
index 000000000000..afd33f851858
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/bluetooth/qcom,ipq5018-bt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm IPQ5018 Bluetooth
+
+maintainers:
+  - George Moussalem <george.moussalem@outlook.com>
+
+properties:
+  compatible:
+    enum:
+      - qcom,ipq5018-bt
+
+  interrupts:
+    items:
+      - description:
+          Interrupt line from the M0 Bluetooth Subsystem to the host processor
+          to notify it of events such as re
+
+  qcom,ipc:
+    $ref: /schemas/types.yaml#/definitions/phandle-array
+    items:
+      - items:
+          - description: phandle to a syscon node representing the APCS registers
+          - description: u32 representing offset to the register within the syscon
+          - description: u32 representing the ipc bit within the register
+    description: |
+      These entries specify the outgoing IPC bit used for signaling the remote
+      M0 BTSS core of a host event or for sending an ACK if the remote processor
+      expects it.
+
+  qcom,rproc:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Phandle to the remote processor node representing the M0 BTSS core.
+
+required:
+  - compatible
+  - interrupts
+  - qcom,ipc
+  - qcom,rproc
+
+allOf:
+  - $ref: bluetooth-controller.yaml#
+  - $ref: qcom,bluetooth-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    bluetooth: bluetooth {
+      compatible = "qcom,ipq5018-bt";
+
+      qcom,ipc = <&apcs_glb 8 23>;
+      interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+      qcom,rproc = <&m0_btss>;
+    };

-- 
2.53.0



^ permalink raw reply related

* [PATCH 3/6] Bluetooth: btqca: Add IPQ5018 support
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>

Add the IPQ5018 SoC type and support for loading its firmware.

The firmware tested has been taken from GPL sources of various router
boards. Firmware files needed are:
- qca/bt_fw_patch.mbn
- qca/mpnv10.bin

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 drivers/bluetooth/btqca.c | 16 ++++++++++++++++
 drivers/bluetooth/btqca.h |  3 +++
 2 files changed, 19 insertions(+)

diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c
index 04ebe290bc78..e136e91976cf 100644
--- a/drivers/bluetooth/btqca.c
+++ b/drivers/bluetooth/btqca.c
@@ -380,6 +380,9 @@ static int qca_tlv_check_data(struct hci_dev *hdev,
 		break;
 
 	case TLV_TYPE_NVM:
+		if (soc_type == QCA_IPQ5018)
+			break;
+
 		if (fw_size < sizeof(struct tlv_type_hdr))
 			return -EINVAL;
 
@@ -794,6 +797,9 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 	else
 		rom_ver = ((soc_ver & 0x00000f00) >> 0x04) | (soc_ver & 0x0000000f);
 
+	if (soc_type == QCA_IPQ5018)
+		goto download_nvm;
+
 	if (soc_type == QCA_WCN6750)
 		qca_send_patch_config_cmd(hdev);
 
@@ -881,6 +887,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 	if (soc_type == QCA_QCA2066 || soc_type == QCA_WCN7850)
 		qca_read_fw_board_id(hdev, &boardid);
 
+download_nvm:
 	/* Download NVM configuration */
 	config.type = TLV_TYPE_NVM;
 	if (firmware_name) {
@@ -939,6 +946,10 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 			qca_get_nvm_name_by_board(config.fwname, sizeof(config.fwname),
 				 "hmtnv", soc_type, ver, rom_ver, boardid);
 			break;
+		case QCA_IPQ5018:
+			snprintf(config.fwname, sizeof(config.fwname),
+				 "qca/mpnv%02x.bin", rom_ver);
+			break;
 		default:
 			snprintf(config.fwname, sizeof(config.fwname),
 				 "qca/nvm_%08x.bin", soc_ver);
@@ -958,6 +969,9 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 		return err;
 	}
 
+	if (soc_type == QCA_IPQ5018)
+		msleep(NVM_READY_DELAY_MS);
+
 	switch (soc_type) {
 	case QCA_QCA2066:
 	case QCA_QCA6390:
@@ -965,6 +979,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 	case QCA_WCN6750:
 	case QCA_WCN6855:
 	case QCA_WCN7850:
+	case QCA_IPQ5018:
 		err = qca_disable_soc_logging(hdev);
 		if (err < 0)
 			return err;
@@ -1001,6 +1016,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 	case QCA_WCN6750:
 	case QCA_WCN6855:
 	case QCA_WCN7850:
+	case QCA_IPQ5018:
 		/* get fw build info */
 		err = qca_read_fw_build_info(hdev);
 		if (err < 0)
diff --git a/drivers/bluetooth/btqca.h b/drivers/bluetooth/btqca.h
index 8f3c1b1c77b3..343cd62d1137 100644
--- a/drivers/bluetooth/btqca.h
+++ b/drivers/bluetooth/btqca.h
@@ -54,6 +54,8 @@
 #define QCA_HSP_GF_SOC_ID		0x1200
 #define QCA_HSP_GF_SOC_MASK		0x0000ff00
 
+#define NVM_READY_DELAY_MS		1500
+
 enum qca_baudrate {
 	QCA_BAUDRATE_115200	= 0,
 	QCA_BAUDRATE_57600,
@@ -158,6 +160,7 @@ enum qca_btsoc_type {
 	QCA_WCN6750,
 	QCA_WCN6855,
 	QCA_WCN7850,
+	QCA_IPQ5018,
 };
 
 #if IS_ENABLED(CONFIG_BT_QCA)

-- 
2.53.0



^ permalink raw reply related

* [PATCH 2/6] remoteproc: qcom: Add M0 BTSS secure PIL driver
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>

Add support to bring up the M0 core of the bluetooth subsystem found in
the IPQ5018 SoC.

The signed firmware loaded is authenticated by TrustZone. If successful,
the M0 core boots the firmware and the peripheral is taken out of reset
using a Secure Channel Manager call to TrustZone.

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 drivers/remoteproc/Kconfig            |  12 ++
 drivers/remoteproc/Makefile           |   1 +
 drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++++++++++++++++++++++++++++++
 3 files changed, 274 insertions(+)

diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index c521c744e7db..6b52f78f1427 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -163,6 +163,18 @@ config PRU_REMOTEPROC
 	  processors on various TI SoCs. It's safe to say N here if you're
 	  not interested in the PRU or if you are unsure.
 
+config QCOM_M0_BTSS_PIL
+	tristate "Qualcomm M0 BTSS Peripheral Image Loader"
+	depends on OF && ARCH_QCOM
+	select QCOM_MDT_LOADER
+	select QCOM_RPROC_COMMON
+	select QCOM_SCM
+	help
+	  Say y here to support the Secure Peripheral Imager Loader for the
+	  Qualcomm Bluetooth Subsystem running on the M0 remote processor found
+	  in the IPQ5018 SoC. The M0 core is started and stopped using a
+	  Secure Channel Manager call to TrustZone.
+
 config QCOM_PIL_INFO
 	tristate
 
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 1c7598b8475d..df80faf8d0df 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_DA8XX_REMOTEPROC)		+= da8xx_remoteproc.o
 obj-$(CONFIG_KEYSTONE_REMOTEPROC)	+= keystone_remoteproc.o
 obj-$(CONFIG_MESON_MX_AO_ARC_REMOTEPROC)+= meson_mx_ao_arc.o
 obj-$(CONFIG_PRU_REMOTEPROC)		+= pru_rproc.o
+obj-$(CONFIG_QCOM_M0_BTSS_PIL)		+= qcom_m0_btss_pil.o
 obj-$(CONFIG_QCOM_PIL_INFO)		+= qcom_pil_info.o
 obj-$(CONFIG_QCOM_RPROC_COMMON)		+= qcom_common.o
 obj-$(CONFIG_QCOM_Q6V5_COMMON)		+= qcom_q6v5.o
diff --git a/drivers/remoteproc/qcom_m0_btss_pil.c b/drivers/remoteproc/qcom_m0_btss_pil.c
new file mode 100644
index 000000000000..7168e270e4d4
--- /dev/null
+++ b/drivers/remoteproc/qcom_m0_btss_pil.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/elf.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/soc/qcom/mdt_loader.h>
+
+#include "qcom_common.h"
+
+#define BTSS_PAS_ID	0xc
+
+struct m0_btss {
+	struct device *dev;
+	phys_addr_t mem_phys;
+	phys_addr_t mem_reloc;
+	void __iomem *mem_region;
+	size_t mem_size;
+	struct reset_control *btss_reset;
+};
+
+static int m0_btss_start(struct rproc *rproc)
+{
+	int ret;
+
+	if (!qcom_scm_pas_supported(BTSS_PAS_ID)) {
+		dev_err(rproc->dev.parent,
+			"PAS is not available for peripheral: 0x%x\n",
+			BTSS_PAS_ID);
+		return -ENODEV;
+	}
+
+	ret = qcom_scm_pas_auth_and_reset(BTSS_PAS_ID);
+	if (ret) {
+		dev_err(rproc->dev.parent, "Failed to start rproc: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int m0_btss_stop(struct rproc *rproc)
+{
+	int ret;
+
+	if (rproc->state == RPROC_RUNNING || rproc->state == RPROC_CRASHED) {
+		ret = qcom_scm_pas_shutdown(BTSS_PAS_ID);
+		if (ret) {
+			dev_err(rproc->dev.parent, "Failed to stop rproc: %d\n",
+				ret);
+			return ret;
+		}
+
+		dev_info(rproc->dev.parent, "Successfully stopped rproc\n");
+	}
+
+	return 0;
+}
+
+static int m0_btss_load(struct rproc *rproc, const struct firmware *fw)
+{
+	struct m0_btss *desc = rproc->priv;
+	const struct elf32_phdr *phdrs;
+	const struct firmware *seg_fw;
+	const struct elf32_phdr *phdr;
+	const struct elf32_hdr *ehdr;
+	void __iomem *metadata;
+	size_t metadata_size;
+	int i, ret;
+
+	ehdr = (const struct elf32_hdr *)fw->data;
+	phdrs = (const struct elf32_phdr *)(ehdr + 1);
+
+	ret = request_firmware(&fw, rproc->firmware, rproc->dev.parent);
+	if (ret) {
+		dev_err(rproc->dev.parent, "Failed to request firmware: %d\n",
+			ret);
+		return ret;
+	}
+
+	metadata = qcom_mdt_read_metadata(fw, &metadata_size, rproc->firmware,
+					  rproc->dev.parent);
+	if (IS_ERR(metadata)) {
+		ret = PTR_ERR(metadata);
+		dev_err(rproc->dev.parent,
+			"Failed to read firmware metadata: %d\n", ret);
+		goto release_fw;
+	}
+
+	ret = qcom_scm_pas_init_image(BTSS_PAS_ID, metadata,
+				      metadata_size, NULL);
+	if (ret) {
+		dev_err(rproc->dev.parent, "PAS init image failed: %d\n", ret);
+		goto free_metadata;
+	}
+
+	for (i = 0; i < ehdr->e_phnum; i++) {
+		char *seg_name __free(kfree) = kstrdup(rproc->firmware,
+						       GFP_KERNEL);
+		if (!seg_name)
+			return -ENOMEM;
+
+		phdr = &phdrs[i];
+
+		/* Only process valid loadable data segments */
+		if (phdr->p_type != PT_LOAD || !phdr->p_memsz)
+			continue;
+
+		if (phdr->p_vaddr + phdr->p_filesz > desc->mem_size) {
+			dev_err(rproc->dev.parent,
+				"Segment data exceeds the reserved memory area!\n");
+			goto free_metadata;
+		}
+
+		/* Check if firmware is split across multiple segment files */
+		if (phdr->p_offset > fw->size ||
+		    phdr->p_offset + phdr->p_filesz > fw->size) {
+			sprintf(seg_name + strlen(seg_name) - 3, "b%02d", i);
+			ret = request_firmware(&seg_fw, seg_name,
+					       rproc->dev.parent);
+			if (ret) {
+				dev_err(rproc->dev.parent,
+					"Could not find split segment binary: %s\n",
+					seg_name);
+				goto free_metadata;
+			}
+
+			/*
+			 * Use the virtual instead of the physical address as
+			 * the offset
+			 */
+			memcpy_toio(desc->mem_region + phdr->p_vaddr,
+				    seg_fw->data, phdr->p_filesz);
+
+			release_firmware(seg_fw);
+		} else {
+			memcpy_toio(desc->mem_region + phdr->p_vaddr,
+				    fw->data + phdr->p_offset, phdr->p_filesz);
+		}
+	}
+
+	return 0;
+
+free_metadata:
+	kfree(metadata);
+release_fw:
+	release_firmware(fw);
+	return ret;
+}
+
+static const struct rproc_ops m0_btss_ops = {
+	.start = m0_btss_start,
+	.stop = m0_btss_stop,
+	.load = m0_btss_load,
+	.get_boot_addr = rproc_elf_get_boot_addr,
+};
+
+static int m0_btss_alloc_memory_region(struct m0_btss *desc)
+{
+	struct device *dev = desc->dev;
+	struct resource res;
+	int ret;
+
+	ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res);
+	if (ret) {
+		dev_err(dev, "unable to acquire memory-region resource\n");
+		return ret;
+	}
+
+	desc->mem_phys = res.start;
+	desc->mem_reloc = res.start;
+	desc->mem_size = resource_size(&res);
+	desc->mem_region = devm_ioremap(dev, desc->mem_phys, desc->mem_size);
+	if (!desc->mem_region) {
+		dev_err(dev, "unable to map memory region: %pR\n", &res);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int m0_btss_pil_probe(struct platform_device *pdev)
+{
+	// struct reset_control *btss_reset;
+	struct device *dev = &pdev->dev;
+	const char *fw_name = NULL;
+	struct m0_btss *desc;
+	struct clk *lpo_clk;
+	struct rproc *rproc;
+	int ret;
+
+	ret = of_property_read_string(dev->of_node, "firmware-name",
+				      &fw_name);
+	if (ret < 0)
+		return ret;
+
+	rproc = devm_rproc_alloc(dev, "m0btss", &m0_btss_ops,
+				 fw_name, sizeof(*desc));
+	if (!rproc) {
+		dev_err(dev, "failed to allocate rproc\n");
+		return -ENOMEM;
+	}
+
+	desc = rproc->priv;
+	desc->dev = dev;
+
+	ret = m0_btss_alloc_memory_region(desc);
+	if (ret)
+		return ret;
+
+	lpo_clk = devm_clk_get_enabled(dev, "btss_lpo_clk");
+	if (IS_ERR(lpo_clk))
+		return dev_err_probe(dev, PTR_ERR(lpo_clk),
+				     "Failed to get lpo clock\n");
+
+	desc->btss_reset = devm_reset_control_get(dev, "btss_reset");
+	if (IS_ERR_OR_NULL(desc->btss_reset))
+		return dev_err_probe(dev, PTR_ERR(desc->btss_reset),
+				     "unable to acquire btss_reset\n");
+
+	ret = reset_control_deassert(desc->btss_reset);
+	if (ret)
+		return dev_err_probe(rproc->dev.parent, ret,
+				     "Failed to deassert reset\n");
+
+	rproc->auto_boot = false;
+	ret = devm_rproc_add(dev, rproc);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, rproc);
+
+	return 0;
+}
+
+static const struct of_device_id m0_btss_of_match[] = {
+	{ .compatible = "qcom,ipq5018-btss-pil" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, m0_btss_of_match);
+
+static struct platform_driver m0_btss_pil_driver = {
+	.probe = m0_btss_pil_probe,
+	.driver = {
+		.name = "qcom-m0-btss-pil",
+		.of_match_table = m0_btss_of_match,
+	},
+};
+
+module_platform_driver(m0_btss_pil_driver);
+
+MODULE_DESCRIPTION("Qualcomm M0 Bluetooth Subsystem Peripheral Image Loader");
+MODULE_LICENSE("GPL");

-- 
2.53.0



^ permalink raw reply related

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

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

* 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

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


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