All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 0/6] Add support for IPQ5018 Bluetooth
@ 2026-07-06 17:26 ` George Moussalem
  0 siblings, 0 replies; 26+ messages in thread
From: George Moussalem via B4 Relay @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Krzysztof Kozlowski, Bartosz Golaszewski,
	Konrad Dybcio, Dmitry Baryshkov

Hello,

This patch series introduces Bluetooth support for IPQ5018.

Bluetooth 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 Bluetooth controller.
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 FW Build: BTFW.MAPLE.1.0.0-00102-MPL_ROM_PATCHZ-1

Best regards,
George Moussalem

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
Changes in v4:
- Ordered switch statements in btqca.c. NOTE: left enum qca_bt_soc_type
  unordered in the header file as the btqca driver has conditionally
  checks whether bt_soc_type is above a certain value:
  'if (soc_type >= QCA_WCN3991)' is found in 5 places
- Fixed TX path serialization by protecting btqcomipc_send and
  btss_send ring access with desc->lock, preventing concurrent updates
  of ring indices and free counters from TX and worker contexts.
- Fixed and simplified firmware ring traversal to decode little-endian
  next and translate firmware offsets via TO_APPS_ADDR in all ring
  walks, avoiding invalid pointer dereferences.
- Added short-message RX length validation (msg_len must not exceed
  IPC_MSG_PLD_SZ when not long message), preventing out-of-bounds read
  from inline shared-memory payloads.
- Fixed RX allocation failure behavior to advance ring index and
  continue, preventing permanent RX ring stall on transient kmalloc
  failures.
- Replace macro for calculting large buffer size that performed unsafe
  arithmetic by an inline function that safely calculates the size by
  reading values using endian functions le**_to_cpu().
- Declared aux_ptr as a zero-initialized struct as opposed to using
  memset and changed struct definition by updating its member to use
  u32 as opposed to __le32 as these values aren't set by firmware and
  need not be validated on endianness.
- Fixed long-message slot leak when short TX ring is full by
  snapshotting and rolling back long-buffer accounting on
  allocation-success/short-ring-fail path.
- Updated firmware load logic to remove call to qcom_mdt_pas_init for
  which the export was recently removed and replaced it by calls to
  read the metadata and the validation of it using SCM.
  NOTE: Sashiko correctly pointed out that the firmware ELF/mdt headers
  weren't validated in the driver. However, this is done by the call to
  qcom_mdt_read_metadata which returns -EINVAL if the mdt header isn't
  valid.
- Hardened firmware segment bounds checks against integer overflow by
  using subtraction form (filesz <= mem_size - vaddr).
- Replaced unsafe firmware split name rewrite with validated stem
  handling and kasprintf formatting, removing underflow/overflow risk.
- Converted workqueue allocation to devm_alloc_ordered_workqueue to fix
  workqueue leak in IRQ registration failure path.
- Added cleanup path in btqcomipc_open to shut down controller on
  startup timeout, avoiding leaving peripheral powered after failed open
- Reordered probe sequence to allocate/attach hdev before IRQ
  registration, eliminating early-spurious-IRQ dereference window for
  desc->hdev.
- Reordered remove path to unregister/free hdev before driver deinit,
  preventing send path from racing against queue/work teardown.
- Corrected HCI command packet length parsing to use 8-bit plen directly
  instead of using le16_to_cpu.
- Fixed endianness and address translation in TX ACK processing path.
  Converted firmware-owned ring metadata before use in btss_process_ack:
  rbuf base offset, msg header, long-message pointer, and message length
  are now read via le32_to_cpu or le16_to_cpu prior to reclaim logic.
- Added explicit producer ordering barrier in TX ring publish path.
  Inserted dma_wmb between payload or header writes and widx update so
  firmware cannot observe producer index advancement before data becomes
  visible.
- Removed info message printing FW build info as that was recently
  introduced in the kernel in commit bdea21b3be43
- Added dependency on OF and ARCH_QCOM symbols to Kconfig. In addition,
  also select QCOM_MDT_LOADER and QCOM_SCM.
- Removed syscon compatible from mailbox node, avoiding dt_binding_check
  failures and update the syscon acquisition logic in driver probe
  accordingly.
- Link to v3: https://lore.kernel.org/r/20260703-ipq5018-bluetooth-v3-0-62da72818ab3@outlook.com

Changes in v3:
- Consolidated the SCM and helper call into one function which now
  checks availability of the SCM call inline.
- Changed argument to boolean type to set ECO power mode on or off as
  opposed to passing a raw power mode value.
- Removed unneeded parentheses in if statement
- Declared aux_ptr as a zero-initialized struct as opposed to using
  memset
- Changed for loop to a while loop for better readability
- Reordered function definition of btqcomipc_update_stats to avoid the
  need for a forward declaration.
- Removed unneeded header files from btqcomipc.c (bits.h, device.h,
  init.h, kernel.h, of_irq.h, types.h)
- Link to v2: https://lore.kernel.org/r/20260629-ipq5018-bluetooth-v2-0-02770f03b6bb@outlook.com

Changes in v2:
- removed unused struct member btss_reset from m0_btss struct.
- used devm_reset_control_get_exclusive_deasserted to acquire and
  deassert btss_reset in one call. No need to explicitly assert upon
  unbind since devres will handle it.
- As per Bjorn's comments, collapsed the rproc and BT driver into one
  which now handles firmware loading and transport.
- As per further review comments and above change, squashed the two
  devicetree bindings into one to represent the Bluetooth controller
- Added new SCM call to drive Bluetooth power modes. Based on testing on
  more router boards, this is required during start/stop sequences to
  avoid the controller hanging. This is only supported on relatively
  newer QSEE versions, so the SCM call is checked for availability
  before use.
- Refactored resource lifecycle management in the driver so that IRQ and
  work queue are managed at the driver level (probe/remove) while FW
  loading and powering on/off the controller is handled upon hdev
  open/close.
- Consolidated TX send functions for custom IPC and HCI frames into one
  and solved a potential NULL pointer dereference issue under TX
  pressure.
- Replace code to load and initialize firmware metadate by existing
  qcom_mdt_pas_init function. 
- Solved an off byone calculation error in the RX parser.
- Added entry to MAINTAINERS file
- Link to v1: https://lore.kernel.org/r/20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com

---
George Moussalem (6):
      dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
      Bluetooth: btqca: Add IPQ5018 support
      firmware: qcom: scm: Add support for setting Bluetooth power modes
      Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
      arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
      MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver

 .../bindings/net/bluetooth/qcom,ipq5018-bt.yaml    |   86 ++
 MAINTAINERS                                        |    7 +
 arch/arm64/boot/dts/qcom/ipq5018.dtsi              |   22 +
 drivers/bluetooth/Kconfig                          |   14 +
 drivers/bluetooth/Makefile                         |    1 +
 drivers/bluetooth/btqca.c                          |   16 +
 drivers/bluetooth/btqca.h                          |    3 +
 drivers/bluetooth/btqcomipc.c                      | 1148 ++++++++++++++++++++
 drivers/firmware/qcom/qcom_scm.c                   |   44 +
 drivers/firmware/qcom/qcom_scm.h                   |    1 +
 include/linux/firmware/qcom/qcom_scm.h             |    1 +
 11 files changed, 1343 insertions(+)
---
base-commit: 4d1ab324fcb7d20df5a071edb0304461846fdc12
change-id: 20260625-ipq5018-bluetooth-06ff66c9d753
prerequisite-message-id: <20260703-block-as-nvmem-v8-0-98ae32bfc49a@oss.qualcomm.com>
prerequisite-patch-id: 467ba5e0fde0e9a1e9d0a6faf3d22ba2126c62a7
prerequisite-patch-id: 9168930e40551e842c8171d5433a6f39ad4b78a4
prerequisite-patch-id: 64fecfbd1e085d7d2ab0ae23295ca34ec8e14c5e
prerequisite-patch-id: 7bf282ed9312b2ffdbe420429bbe355622620272
prerequisite-patch-id: 29a7d652948d13f91bdd4fc5fb8e6dbcd6e5fcc5
prerequisite-patch-id: c03136820a7749fd9630080e6d4459a298b3b630
prerequisite-patch-id: 43975fc425bf2f5dd5c1de66aaa78c4b4bde568c
prerequisite-patch-id: 75caa99e3bbcdf41b6462b9f5f703bea1d4a65fa
prerequisite-patch-id: b9b2a2f80a4827b457166b20f54644e57d90b824

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



^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH v4 0/6] Add support for IPQ5018 Bluetooth
@ 2026-07-06 17:26 ` George Moussalem
  0 siblings, 0 replies; 26+ messages in thread
From: George Moussalem @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Krzysztof Kozlowski, Bartosz Golaszewski,
	Konrad Dybcio, Dmitry Baryshkov

Hello,

This patch series introduces Bluetooth support for IPQ5018.

Bluetooth 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 Bluetooth controller.
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 FW Build: BTFW.MAPLE.1.0.0-00102-MPL_ROM_PATCHZ-1

Best regards,
George Moussalem

Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
Changes in v4:
- Ordered switch statements in btqca.c. NOTE: left enum qca_bt_soc_type
  unordered in the header file as the btqca driver has conditionally
  checks whether bt_soc_type is above a certain value:
  'if (soc_type >= QCA_WCN3991)' is found in 5 places
- Fixed TX path serialization by protecting btqcomipc_send and
  btss_send ring access with desc->lock, preventing concurrent updates
  of ring indices and free counters from TX and worker contexts.
- Fixed and simplified firmware ring traversal to decode little-endian
  next and translate firmware offsets via TO_APPS_ADDR in all ring
  walks, avoiding invalid pointer dereferences.
- Added short-message RX length validation (msg_len must not exceed
  IPC_MSG_PLD_SZ when not long message), preventing out-of-bounds read
  from inline shared-memory payloads.
- Fixed RX allocation failure behavior to advance ring index and
  continue, preventing permanent RX ring stall on transient kmalloc
  failures.
- Replace macro for calculting large buffer size that performed unsafe
  arithmetic by an inline function that safely calculates the size by
  reading values using endian functions le**_to_cpu().
- Declared aux_ptr as a zero-initialized struct as opposed to using
  memset and changed struct definition by updating its member to use
  u32 as opposed to __le32 as these values aren't set by firmware and
  need not be validated on endianness.
- Fixed long-message slot leak when short TX ring is full by
  snapshotting and rolling back long-buffer accounting on
  allocation-success/short-ring-fail path.
- Updated firmware load logic to remove call to qcom_mdt_pas_init for
  which the export was recently removed and replaced it by calls to
  read the metadata and the validation of it using SCM.
  NOTE: Sashiko correctly pointed out that the firmware ELF/mdt headers
  weren't validated in the driver. However, this is done by the call to
  qcom_mdt_read_metadata which returns -EINVAL if the mdt header isn't
  valid.
- Hardened firmware segment bounds checks against integer overflow by
  using subtraction form (filesz <= mem_size - vaddr).
- Replaced unsafe firmware split name rewrite with validated stem
  handling and kasprintf formatting, removing underflow/overflow risk.
- Converted workqueue allocation to devm_alloc_ordered_workqueue to fix
  workqueue leak in IRQ registration failure path.
- Added cleanup path in btqcomipc_open to shut down controller on
  startup timeout, avoiding leaving peripheral powered after failed open
- Reordered probe sequence to allocate/attach hdev before IRQ
  registration, eliminating early-spurious-IRQ dereference window for
  desc->hdev.
- Reordered remove path to unregister/free hdev before driver deinit,
  preventing send path from racing against queue/work teardown.
- Corrected HCI command packet length parsing to use 8-bit plen directly
  instead of using le16_to_cpu.
- Fixed endianness and address translation in TX ACK processing path.
  Converted firmware-owned ring metadata before use in btss_process_ack:
  rbuf base offset, msg header, long-message pointer, and message length
  are now read via le32_to_cpu or le16_to_cpu prior to reclaim logic.
- Added explicit producer ordering barrier in TX ring publish path.
  Inserted dma_wmb between payload or header writes and widx update so
  firmware cannot observe producer index advancement before data becomes
  visible.
- Removed info message printing FW build info as that was recently
  introduced in the kernel in commit bdea21b3be43
- Added dependency on OF and ARCH_QCOM symbols to Kconfig. In addition,
  also select QCOM_MDT_LOADER and QCOM_SCM.
- Removed syscon compatible from mailbox node, avoiding dt_binding_check
  failures and update the syscon acquisition logic in driver probe
  accordingly.
- Link to v3: https://lore.kernel.org/r/20260703-ipq5018-bluetooth-v3-0-62da72818ab3@outlook.com

Changes in v3:
- Consolidated the SCM and helper call into one function which now
  checks availability of the SCM call inline.
- Changed argument to boolean type to set ECO power mode on or off as
  opposed to passing a raw power mode value.
- Removed unneeded parentheses in if statement
- Declared aux_ptr as a zero-initialized struct as opposed to using
  memset
- Changed for loop to a while loop for better readability
- Reordered function definition of btqcomipc_update_stats to avoid the
  need for a forward declaration.
- Removed unneeded header files from btqcomipc.c (bits.h, device.h,
  init.h, kernel.h, of_irq.h, types.h)
- Link to v2: https://lore.kernel.org/r/20260629-ipq5018-bluetooth-v2-0-02770f03b6bb@outlook.com

Changes in v2:
- removed unused struct member btss_reset from m0_btss struct.
- used devm_reset_control_get_exclusive_deasserted to acquire and
  deassert btss_reset in one call. No need to explicitly assert upon
  unbind since devres will handle it.
- As per Bjorn's comments, collapsed the rproc and BT driver into one
  which now handles firmware loading and transport.
- As per further review comments and above change, squashed the two
  devicetree bindings into one to represent the Bluetooth controller
- Added new SCM call to drive Bluetooth power modes. Based on testing on
  more router boards, this is required during start/stop sequences to
  avoid the controller hanging. This is only supported on relatively
  newer QSEE versions, so the SCM call is checked for availability
  before use.
- Refactored resource lifecycle management in the driver so that IRQ and
  work queue are managed at the driver level (probe/remove) while FW
  loading and powering on/off the controller is handled upon hdev
  open/close.
- Consolidated TX send functions for custom IPC and HCI frames into one
  and solved a potential NULL pointer dereference issue under TX
  pressure.
- Replace code to load and initialize firmware metadate by existing
  qcom_mdt_pas_init function. 
- Solved an off byone calculation error in the RX parser.
- Added entry to MAINTAINERS file
- Link to v1: https://lore.kernel.org/r/20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com

---
George Moussalem (6):
      dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
      Bluetooth: btqca: Add IPQ5018 support
      firmware: qcom: scm: Add support for setting Bluetooth power modes
      Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
      arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
      MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver

 .../bindings/net/bluetooth/qcom,ipq5018-bt.yaml    |   86 ++
 MAINTAINERS                                        |    7 +
 arch/arm64/boot/dts/qcom/ipq5018.dtsi              |   22 +
 drivers/bluetooth/Kconfig                          |   14 +
 drivers/bluetooth/Makefile                         |    1 +
 drivers/bluetooth/btqca.c                          |   16 +
 drivers/bluetooth/btqca.h                          |    3 +
 drivers/bluetooth/btqcomipc.c                      | 1148 ++++++++++++++++++++
 drivers/firmware/qcom/qcom_scm.c                   |   44 +
 drivers/firmware/qcom/qcom_scm.h                   |    1 +
 include/linux/firmware/qcom/qcom_scm.h             |    1 +
 11 files changed, 1343 insertions(+)
---
base-commit: 4d1ab324fcb7d20df5a071edb0304461846fdc12
change-id: 20260625-ipq5018-bluetooth-06ff66c9d753
prerequisite-message-id: <20260703-block-as-nvmem-v8-0-98ae32bfc49a@oss.qualcomm.com>
prerequisite-patch-id: 467ba5e0fde0e9a1e9d0a6faf3d22ba2126c62a7
prerequisite-patch-id: 9168930e40551e842c8171d5433a6f39ad4b78a4
prerequisite-patch-id: 64fecfbd1e085d7d2ab0ae23295ca34ec8e14c5e
prerequisite-patch-id: 7bf282ed9312b2ffdbe420429bbe355622620272
prerequisite-patch-id: 29a7d652948d13f91bdd4fc5fb8e6dbcd6e5fcc5
prerequisite-patch-id: c03136820a7749fd9630080e6d4459a298b3b630
prerequisite-patch-id: 43975fc425bf2f5dd5c1de66aaa78c4b4bde568c
prerequisite-patch-id: 75caa99e3bbcdf41b6462b9f5f703bea1d4a65fa
prerequisite-patch-id: b9b2a2f80a4827b457166b20f54644e57d90b824

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


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH v4 1/6] dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
  2026-07-06 17:26 ` George Moussalem
@ 2026-07-06 17:26   ` George Moussalem
  -1 siblings, 0 replies; 26+ messages in thread
From: George Moussalem via B4 Relay @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Krzysztof Kozlowski, Bartosz Golaszewski

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

Document the Qualcomm IPQ5018 Bluetooth controller.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 .../bindings/net/bluetooth/qcom,ipq5018-bt.yaml    | 86 ++++++++++++++++++++++
 1 file changed, 86 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..2119162994e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
@@ -0,0 +1,86 @@
+# 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
+
+  clocks:
+    items:
+      - description: Bluetooth Subsystem low power oscillator clock
+
+  clock-names:
+    items:
+      - const: lpo
+
+  firmware-name:
+    maxItems: 1
+
+  memory-region:
+    items:
+      - description: |
+          Reserved memory carveout for firmware loading and runtime data
+          transport between the host and the Bluetooth controller.
+
+  interrupts:
+    maxItems: 1
+
+  qcom,ipc:
+    $ref: /schemas/types.yaml#/definitions/phandle-array
+    items:
+      - items:
+          - description: syscon node representing the APCS registers
+          - description: offset to the register within the syscon
+          - description: IPC bit within the register
+    description: |
+      The outgoing IPC bit used for signaling the Bluetooth controller of a host
+      event or for sending an ACK if the remote processor expects it.
+
+  resets:
+    items:
+      - description: Bluetooth Subsystem reset
+
+required:
+  - compatible
+  - clocks
+  - clock-names
+  - firmware-name
+  - interrupts
+  - qcom,ipc
+  - resets
+
+allOf:
+  - $ref: bluetooth-controller.yaml#
+  - $ref: qcom,bluetooth-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/qcom,gcc-ipq5018.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/reset/qcom,gcc-ipq5018.h>
+
+    bluetooth {
+      compatible = "qcom,ipq5018-bt";
+
+      firmware-name = "qca/bt_fw_patch.mbn";
+
+      clocks = <&gcc GCC_BTSS_LPO_CLK>;
+      clock-names = "lpo";
+      resets = <&gcc GCC_BTSS_BCR>;
+
+      qcom,ipc = <&apcs_glb 8 23>;
+      interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+      memory-region = <&btss_region>;
+    };

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v4 1/6] dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
@ 2026-07-06 17:26   ` George Moussalem
  0 siblings, 0 replies; 26+ messages in thread
From: George Moussalem @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Krzysztof Kozlowski, Bartosz Golaszewski

Document the Qualcomm IPQ5018 Bluetooth controller.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 .../bindings/net/bluetooth/qcom,ipq5018-bt.yaml    | 86 ++++++++++++++++++++++
 1 file changed, 86 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..2119162994e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
@@ -0,0 +1,86 @@
+# 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
+
+  clocks:
+    items:
+      - description: Bluetooth Subsystem low power oscillator clock
+
+  clock-names:
+    items:
+      - const: lpo
+
+  firmware-name:
+    maxItems: 1
+
+  memory-region:
+    items:
+      - description: |
+          Reserved memory carveout for firmware loading and runtime data
+          transport between the host and the Bluetooth controller.
+
+  interrupts:
+    maxItems: 1
+
+  qcom,ipc:
+    $ref: /schemas/types.yaml#/definitions/phandle-array
+    items:
+      - items:
+          - description: syscon node representing the APCS registers
+          - description: offset to the register within the syscon
+          - description: IPC bit within the register
+    description: |
+      The outgoing IPC bit used for signaling the Bluetooth controller of a host
+      event or for sending an ACK if the remote processor expects it.
+
+  resets:
+    items:
+      - description: Bluetooth Subsystem reset
+
+required:
+  - compatible
+  - clocks
+  - clock-names
+  - firmware-name
+  - interrupts
+  - qcom,ipc
+  - resets
+
+allOf:
+  - $ref: bluetooth-controller.yaml#
+  - $ref: qcom,bluetooth-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/qcom,gcc-ipq5018.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/reset/qcom,gcc-ipq5018.h>
+
+    bluetooth {
+      compatible = "qcom,ipq5018-bt";
+
+      firmware-name = "qca/bt_fw_patch.mbn";
+
+      clocks = <&gcc GCC_BTSS_LPO_CLK>;
+      clock-names = "lpo";
+      resets = <&gcc GCC_BTSS_BCR>;
+
+      qcom,ipc = <&apcs_glb 8 23>;
+      interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+      memory-region = <&btss_region>;
+    };

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v4 2/6] Bluetooth: btqca: Add IPQ5018 support
  2026-07-06 17:26 ` George Moussalem
@ 2026-07-06 17:26   ` George Moussalem
  -1 siblings, 0 replies; 26+ messages in thread
From: George Moussalem via B4 Relay @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Bartosz Golaszewski

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

Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
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..08375664408b 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) {
@@ -894,6 +901,10 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 		}
 	} else {
 		switch (soc_type) {
+		case QCA_IPQ5018:
+			snprintf(config.fwname, sizeof(config.fwname),
+				 "qca/mpnv%02x.bin", rom_ver);
+			break;
 		case QCA_QCA2066:
 			qca_get_nvm_name_by_board(config.fwname,
 						  sizeof(config.fwname),
@@ -958,7 +969,11 @@ 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_IPQ5018:
 	case QCA_QCA2066:
 	case QCA_QCA6390:
 	case QCA_WCN3991:
@@ -997,6 +1012,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 	}
 
 	switch (soc_type) {
+	case QCA_IPQ5018:
 	case QCA_WCN3991:
 	case QCA_WCN6750:
 	case QCA_WCN6855:
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	[flat|nested] 26+ messages in thread

* [PATCH v4 2/6] Bluetooth: btqca: Add IPQ5018 support
@ 2026-07-06 17:26   ` George Moussalem
  0 siblings, 0 replies; 26+ messages in thread
From: George Moussalem @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Bartosz Golaszewski

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

Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
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..08375664408b 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) {
@@ -894,6 +901,10 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 		}
 	} else {
 		switch (soc_type) {
+		case QCA_IPQ5018:
+			snprintf(config.fwname, sizeof(config.fwname),
+				 "qca/mpnv%02x.bin", rom_ver);
+			break;
 		case QCA_QCA2066:
 			qca_get_nvm_name_by_board(config.fwname,
 						  sizeof(config.fwname),
@@ -958,7 +969,11 @@ 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_IPQ5018:
 	case QCA_QCA2066:
 	case QCA_QCA6390:
 	case QCA_WCN3991:
@@ -997,6 +1012,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
 	}
 
 	switch (soc_type) {
+	case QCA_IPQ5018:
 	case QCA_WCN3991:
 	case QCA_WCN6750:
 	case QCA_WCN6855:
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	[flat|nested] 26+ messages in thread

* [PATCH v4 3/6] firmware: qcom: scm: Add support for setting Bluetooth power modes
  2026-07-06 17:26 ` George Moussalem
@ 2026-07-06 17:26   ` George Moussalem
  -1 siblings, 0 replies; 26+ messages in thread
From: George Moussalem via B4 Relay @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Bartosz Golaszewski, Konrad Dybcio,
	Dmitry Baryshkov

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

The Bluetooth subsystem (BTSS) on the IPQ5018 SoC supports setting power
modes which are required to be configured through a Secure Channel
Manager (SCM) call to TrustZone. However, not all Trusted Execution
Environment (QSEE) images support this call, so first check if the call
is available.

Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 drivers/firmware/qcom/qcom_scm.c       | 44 ++++++++++++++++++++++++++++++++++
 drivers/firmware/qcom/qcom_scm.h       |  1 +
 include/linux/firmware/qcom/qcom_scm.h |  1 +
 3 files changed, 46 insertions(+)

diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
index 6b601a4b89db..a34e0f75df3c 100644
--- a/drivers/firmware/qcom/qcom_scm.c
+++ b/drivers/firmware/qcom/qcom_scm.c
@@ -1094,6 +1094,50 @@ bool qcom_scm_pas_supported(u32 pas_id)
 }
 EXPORT_SYMBOL_GPL(qcom_scm_pas_supported);
 
+/**
+ * qcom_scm_pas_set_bluetooth_power_mode() - Configure power mode for the
+ *					     Bluetooth subsystem (BTSS)
+ * @pas_id:	peripheral authentication service id
+ * @eco_mode:	true for ECO mode, false for normal operation
+ *
+ * Return: 0 on success, negative errno on failure.
+ * Returns -EOPNOTSUPP if the firmware configuration call is unavailable.
+ */
+int qcom_scm_pas_set_bluetooth_power_mode(u32 pas_id, bool eco_mode)
+{
+	struct qcom_scm_desc desc = {
+		.svc = QCOM_SCM_SVC_PIL,
+		.cmd = QCOM_SCM_PIL_PAS_BT_PWR_MODE,
+		.arginfo = QCOM_SCM_ARGS(2),
+		.args[0] = pas_id,
+		.args[1] = eco_mode ? 0x4 : 0x0,
+		.owner = ARM_SMCCC_OWNER_SIP,
+	};
+	struct qcom_scm_res res;
+	int ret;
+
+	if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL,
+					  QCOM_SCM_PIL_PAS_BT_PWR_MODE))
+		return -EOPNOTSUPP;
+
+	ret = qcom_scm_clk_enable();
+	if (ret)
+		return ret;
+
+	ret = qcom_scm_bw_enable();
+	if (ret)
+		goto disable_clk;
+
+	ret = qcom_scm_call(__scm->dev, &desc, &res);
+	qcom_scm_bw_disable();
+
+disable_clk:
+	qcom_scm_clk_disable();
+
+	return ret ? : res.result[0];
+}
+EXPORT_SYMBOL_GPL(qcom_scm_pas_set_bluetooth_power_mode);
+
 static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
 {
 	struct qcom_scm_desc desc = {
diff --git a/drivers/firmware/qcom/qcom_scm.h b/drivers/firmware/qcom/qcom_scm.h
index caab80a73e17..5579df5a2aca 100644
--- a/drivers/firmware/qcom/qcom_scm.h
+++ b/drivers/firmware/qcom/qcom_scm.h
@@ -105,6 +105,7 @@ int qcom_scm_shm_bridge_enable(struct device *scm_dev);
 #define QCOM_SCM_PIL_PAS_SHUTDOWN	0x06
 #define QCOM_SCM_PIL_PAS_IS_SUPPORTED	0x07
 #define QCOM_SCM_PIL_PAS_MSS_RESET	0x0a
+#define QCOM_SCM_PIL_PAS_BT_PWR_MODE	0x21
 #define QCOM_SCM_PIL_PAS_GET_RSCTABLE	0x21
 
 #define QCOM_SCM_SVC_IO			0x05
diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h
index 5747bd191bf1..aca937ac8ddd 100644
--- a/include/linux/firmware/qcom/qcom_scm.h
+++ b/include/linux/firmware/qcom/qcom_scm.h
@@ -93,6 +93,7 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c
 						  size_t *output_rt_size);
 
 int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx);
+int qcom_scm_pas_set_bluetooth_power_mode(u32 pas_id, bool eco_mode);
 
 int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val);
 int qcom_scm_io_writel(phys_addr_t addr, unsigned int val);

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v4 3/6] firmware: qcom: scm: Add support for setting Bluetooth power modes
@ 2026-07-06 17:26   ` George Moussalem
  0 siblings, 0 replies; 26+ messages in thread
From: George Moussalem @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Bartosz Golaszewski, Konrad Dybcio,
	Dmitry Baryshkov

The Bluetooth subsystem (BTSS) on the IPQ5018 SoC supports setting power
modes which are required to be configured through a Secure Channel
Manager (SCM) call to TrustZone. However, not all Trusted Execution
Environment (QSEE) images support this call, so first check if the call
is available.

Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 drivers/firmware/qcom/qcom_scm.c       | 44 ++++++++++++++++++++++++++++++++++
 drivers/firmware/qcom/qcom_scm.h       |  1 +
 include/linux/firmware/qcom/qcom_scm.h |  1 +
 3 files changed, 46 insertions(+)

diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c
index 6b601a4b89db..a34e0f75df3c 100644
--- a/drivers/firmware/qcom/qcom_scm.c
+++ b/drivers/firmware/qcom/qcom_scm.c
@@ -1094,6 +1094,50 @@ bool qcom_scm_pas_supported(u32 pas_id)
 }
 EXPORT_SYMBOL_GPL(qcom_scm_pas_supported);
 
+/**
+ * qcom_scm_pas_set_bluetooth_power_mode() - Configure power mode for the
+ *					     Bluetooth subsystem (BTSS)
+ * @pas_id:	peripheral authentication service id
+ * @eco_mode:	true for ECO mode, false for normal operation
+ *
+ * Return: 0 on success, negative errno on failure.
+ * Returns -EOPNOTSUPP if the firmware configuration call is unavailable.
+ */
+int qcom_scm_pas_set_bluetooth_power_mode(u32 pas_id, bool eco_mode)
+{
+	struct qcom_scm_desc desc = {
+		.svc = QCOM_SCM_SVC_PIL,
+		.cmd = QCOM_SCM_PIL_PAS_BT_PWR_MODE,
+		.arginfo = QCOM_SCM_ARGS(2),
+		.args[0] = pas_id,
+		.args[1] = eco_mode ? 0x4 : 0x0,
+		.owner = ARM_SMCCC_OWNER_SIP,
+	};
+	struct qcom_scm_res res;
+	int ret;
+
+	if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL,
+					  QCOM_SCM_PIL_PAS_BT_PWR_MODE))
+		return -EOPNOTSUPP;
+
+	ret = qcom_scm_clk_enable();
+	if (ret)
+		return ret;
+
+	ret = qcom_scm_bw_enable();
+	if (ret)
+		goto disable_clk;
+
+	ret = qcom_scm_call(__scm->dev, &desc, &res);
+	qcom_scm_bw_disable();
+
+disable_clk:
+	qcom_scm_clk_disable();
+
+	return ret ? : res.result[0];
+}
+EXPORT_SYMBOL_GPL(qcom_scm_pas_set_bluetooth_power_mode);
+
 static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
 {
 	struct qcom_scm_desc desc = {
diff --git a/drivers/firmware/qcom/qcom_scm.h b/drivers/firmware/qcom/qcom_scm.h
index caab80a73e17..5579df5a2aca 100644
--- a/drivers/firmware/qcom/qcom_scm.h
+++ b/drivers/firmware/qcom/qcom_scm.h
@@ -105,6 +105,7 @@ int qcom_scm_shm_bridge_enable(struct device *scm_dev);
 #define QCOM_SCM_PIL_PAS_SHUTDOWN	0x06
 #define QCOM_SCM_PIL_PAS_IS_SUPPORTED	0x07
 #define QCOM_SCM_PIL_PAS_MSS_RESET	0x0a
+#define QCOM_SCM_PIL_PAS_BT_PWR_MODE	0x21
 #define QCOM_SCM_PIL_PAS_GET_RSCTABLE	0x21
 
 #define QCOM_SCM_SVC_IO			0x05
diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h
index 5747bd191bf1..aca937ac8ddd 100644
--- a/include/linux/firmware/qcom/qcom_scm.h
+++ b/include/linux/firmware/qcom/qcom_scm.h
@@ -93,6 +93,7 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c
 						  size_t *output_rt_size);
 
 int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx);
+int qcom_scm_pas_set_bluetooth_power_mode(u32 pas_id, bool eco_mode);
 
 int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val);
 int qcom_scm_io_writel(phys_addr_t addr, unsigned int val);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v4 4/6] Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
  2026-07-06 17:26 ` George Moussalem
@ 2026-07-06 17:26   ` George Moussalem
  -1 siblings, 0 replies; 26+ messages in thread
From: George Moussalem via B4 Relay @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem

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

Add support for the Bluetooth controller found in the IPQ5018 SoC.
This driver implements firmware loading and the transport layer between
the HCI core and the Bluetooth controller.

The firmware is loaded by the host into the 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 controller.

An outgoing APCS IPC bit and an incoming GIC interrupt handle host/guest
signaling.

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

diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 4e8c24d757e9..a2d82f27f07a 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -413,6 +413,20 @@ 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"
+	depends on OF && ARCH_QCOM
+	select BT_QCA
+	select QCOM_MDT_LOADER
+	select QCOM_SCM
+	help
+	  Qualcomm IPQ5018 IPC based HCI driver.
+	  This driver is used to load firmware and bridge HCI data onto shared
+	  memory between the host and the Bluetooth controller.
+
+	  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..bea3b83d9f2e
--- /dev/null
+++ b/drivers/bluetooth/btqcomipc.c
@@ -0,0 +1,1148 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/elf.h>
+#include <linux/firmware.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/mdt_loader.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 by IPC interrupt is required by firmware.
+ *
+ * - 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	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 BTSS_PAS_ID		0xc
+
+#define BTSS_PWR_CTRL_DELAY_MS	50
+
+struct long_msg_info {
+	u16 smsg_free_cnt;
+	u16 lmsg_free_cnt;
+	u8 ridx;
+	u8 widx;
+} __packed;
+
+struct ipc_aux_ptr {
+	u32 len;
+	u32 buf;
+};
+
+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 hci_dev *hdev;
+
+	struct regmap *regmap;
+	u32 offset;
+	u32 bit;
+	int irq;
+
+	const char *firmware;
+	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 inline u32 btss_lbuf_size(struct qcom_btss *desc,
+				 const struct context_info *ctxt)
+{
+	u32 total_size = le16_to_cpu(ctxt->total_size);
+	u32 lring_buf = le32_to_cpu(ctxt->lring_buf);
+
+	return ((TO_BT_ADDR((void *)ctxt) + total_size) -
+		lring_buf) / ctxt->lmsg_buf_cnt;
+}
+
+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 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 = btss_lbuf_size(desc, desc->tx_ctxt);
+	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(le32_to_cpu(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)
+{
+	struct ring_buffer_info *rinfo;
+	u32 next;
+	u8 idx;
+
+	rinfo = &(desc->tx_ctxt->sring_buf_info);
+	while (rinfo != NULL) {
+		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;
+		}
+
+		next = le32_to_cpu(rinfo->next);
+		rinfo = next ? (struct ring_buffer_info *)TO_APPS_ADDR(next) : NULL;
+	}
+
+	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 ipc_aux_ptr aux_ptr = { };
+	struct ring_buffer_info *rinfo;
+	struct ring_buffer *rbuf;
+	bool is_lbuf_full = false;
+	bool is_sbuf_full = false;
+	u16 hdr = msg_hdr;
+	u16 lmsg_free_cnt;
+	void *ptr_buf;
+	u8 lmsg_widx;
+	u32 len;
+
+	/* Account for HCI packet type as it's not included in the skb payload */
+	len = skb ? skb->len + 1 : 0;
+
+	if (len > IPC_MSG_PLD_SZ) {
+		hdr |= IPC_HDR_LONG_MSG;
+
+		/* snapshot of long message context in case we need to reverse */
+		lmsg_free_cnt = le16_to_cpu(desc->lmsg_ctxt.lmsg_free_cnt);
+		lmsg_widx = desc->lmsg_ctxt.widx;
+
+		ptr_buf = btss_alloc_lmsg(desc, len,
+					  &aux_ptr, &is_lbuf_full);
+		if (IS_ERR(ptr_buf)) {
+			bt_dev_err(hdev, "TX long buffers 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, "TX short buffers full");
+		hdev->stat.err_tx++;
+		if (len > IPC_MSG_PLD_SZ) {
+			/* reverse long message context if we allocated one */
+			desc->lmsg_ctxt.lmsg_free_cnt = cpu_to_le16(lmsg_free_cnt);
+			desc->lmsg_ctxt.widx = lmsg_widx;
+		}
+		return PTR_ERR(rinfo);
+	}
+
+	rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(le32_to_cpu(rinfo->rbuf))))[rinfo->widx];
+
+	if (!skb)
+		goto complete_tx;
+
+	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 free buffer count is low, send ACK request to signal to the
+	 * firmware to process and free up queued buffers in the TX ring.
+	 */
+	if (is_sbuf_full || is_lbuf_full)
+		hdr |= IPC_HDR_REQ_ACK;
+
+complete_tx:
+	rbuf->msg_hdr = cpu_to_le16(hdr);
+	rbuf->len = cpu_to_le16(len);
+
+	dma_wmb();
+
+	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 = btss_lbuf_size(desc, desc->tx_ctxt);
+
+	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_recv_cust_frame(struct qcom_btss *desc, u8 cmd)
+{
+	u16 msg_hdr = 0;
+	int ret;
+
+	switch (cmd) {
+	case IPC_CMD_STOP:
+		spin_unlock(&desc->lock);
+		ret = qcom_scm_pas_set_bluetooth_power_mode(BTSS_PAS_ID, true);
+		spin_lock(&desc->lock);
+		if (ret && ret != -EOPNOTSUPP) {
+			bt_dev_err(desc->hdev,
+				   "Failed to apply BTSS power-save mode: %d",
+				   ret);
+			return ret;
+		}
+
+		WRITE_ONCE(desc->running, false);
+
+		msg_hdr |= cmd;
+		ret = btss_send(desc, msg_hdr, NULL);
+		if (ret)
+			bt_dev_err(desc->hdev,
+				   "Failed to send control message");
+		break;
+	case IPC_CMD_START:
+		spin_unlock(&desc->lock);
+		ret = qcom_scm_pas_set_bluetooth_power_mode(BTSS_PAS_ID, false);
+		spin_lock(&desc->lock);
+		if (ret && ret != -EOPNOTSUPP) {
+			bt_dev_err(desc->hdev,
+				   "Failed to apply BTSS active power mode: %d",
+				   ret);
+			return ret;
+		}
+
+		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);
+		break;
+	default:
+		bt_dev_err(desc->hdev, "Unsupported CMD ID: %u", cmd);
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+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 < 1 + 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 < 1 + HCI_COMMAND_HDR_SIZE)
+			return -EILSEQ;
+		struct hci_command_hdr *hdr = (struct hci_command_hdr *)(data + 1);
+
+		pkt_len = HCI_COMMAND_HDR_SIZE + hdr->plen;
+		break;
+	}
+	case HCI_ACLDATA_PKT:
+	{
+		if (len < 1 + 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 < 1 + 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 + 1 > 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;
+	u16 msg_hdr;
+	u16 msg_len;
+	u32 lsz;
+	int ret;
+
+	ridx = rinfo->ridx;
+
+	while (ridx != rinfo->widx) {
+		rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(le32_to_cpu(rinfo->rbuf))))[ridx];
+		msg_hdr = le16_to_cpu(rbuf->msg_hdr);
+		msg_len = le16_to_cpu(rbuf->len);
+
+		if (!(msg_hdr & IPC_HDR_LONG_MSG) && msg_len > IPC_MSG_PLD_SZ) {
+			desc->hdev->stat.err_rx++;
+			bt_dev_err(desc->hdev,
+				   "Dropping short RX frame with invalid len %u",
+				   msg_len);
+			ridx = (ridx + 1) % rinfo->ring_buf_cnt;
+			rinfo->ridx = ridx;
+			if (rx_count)
+				(*rx_count)++;
+			continue;
+		}
+
+		if (msg_hdr & IPC_HDR_LONG_MSG) {
+			rxbuf = TO_APPS_ADDR(le32_to_cpu(rbuf->payload.lmsg_data));
+			lsz = btss_lbuf_size(desc, desc->rx_ctxt);
+
+			if (IS_RX_MEM_NON_CONTIGIOUS(le32_to_cpu(rbuf->payload.lmsg_data),
+					     msg_len, lsz)) {
+				lbuf_idx = GET_RX_INDEX_FROM_BUF(
+						le32_to_cpu(rbuf->payload.lmsg_data), lsz);
+
+				blks_consumed = desc->rx_ctxt->lmsg_buf_cnt -
+					lbuf_idx;
+				aux_ptr.len = msg_len - (blks_consumed * lsz);
+				aux_ptr.buf = le32_to_cpu(desc->rx_ctxt->lring_buf);
+			}
+		} else {
+			rxbuf = rbuf->payload.smsg_data;
+		}
+
+		*ack = msg_hdr & IPC_HDR_REQ_ACK;
+
+		pkt_type = FIELD_GET(IPC_HDR_PKT_TYPE_MASK, msg_hdr);
+
+		switch (pkt_type) {
+		case IPC_HDR_PKT_TYPE_HCI:
+			buf = kmalloc(msg_len, GFP_ATOMIC);
+			if (!buf) {
+				desc->hdev->stat.err_rx++;
+				bt_dev_err(desc->hdev,
+					   "Dropping HCI frame due to RX allocation failure");
+				ridx = (ridx + 1) % rinfo->ring_buf_cnt;
+				rinfo->ridx = ridx;
+				if (rx_count)
+					(*rx_count)++;
+				continue;
+			}
+
+			memcpy_fromio(buf, rxbuf, msg_len - aux_ptr.len);
+
+			if (aux_ptr.buf)
+				memcpy_fromio(buf + (msg_len - aux_ptr.len),
+					      TO_APPS_ADDR(aux_ptr.buf),
+					      aux_ptr.len);
+
+			ret = btss_recv_hci_frame(desc, buf, msg_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, msg_hdr);
+			ret = btss_recv_cust_frame(desc, cmd);
+			if (ret)
+				bt_dev_warn(desc->hdev,
+					    "Failed to process custom frame: %d",
+					    ret);
+			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;
+	u32 next;
+	u32 lmsg;
+	u16 msg_hdr;
+	u16 msg_len;
+	u8 tidx;
+
+	rinfo = &desc->tx_ctxt->sring_buf_info;
+	while (rinfo != NULL) {
+		tidx = rinfo->tidx;
+		rbuf = (struct ring_buffer *)TO_APPS_ADDR(le32_to_cpu(rinfo->rbuf));
+
+		while (tidx != rinfo->ridx) {
+			msg_hdr = le16_to_cpu(rbuf[tidx].msg_hdr);
+			if (msg_hdr & IPC_HDR_LONG_MSG) {
+				lmsg = le32_to_cpu(rbuf[tidx].payload.lmsg_data);
+				msg_len = le16_to_cpu(rbuf[tidx].len);
+				btss_free_lmsg(desc, lmsg, msg_len);
+			}
+
+			tidx = (tidx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+			desc->lmsg_ctxt.smsg_free_cnt++;
+		}
+
+		rinfo->tidx = tidx;
+
+		btss_process_tx_queue(desc);
+
+		next = le32_to_cpu(rinfo->next);
+		rinfo = next ? (struct ring_buffer_info *)TO_APPS_ADDR(next) : NULL;
+	}
+}
+
+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, next;
+	int ret;
+
+	spin_lock(&desc->lock);
+
+	if (unlikely(!READ_ONCE(desc->running))) {
+		/*
+		 * FW sets offset of RX context info at the start of the memory
+		 * region upon boot
+		 */
+		offset = readl(desc->mem_region);
+		dev_dbg(desc->dev, "offset after firmware boot: 0x%08x\n",
+			offset);
+		desc->rx_ctxt = (struct context_info *)(desc->mem_region + offset);
+	} else {
+		btss_process_ack(desc);
+	}
+
+	rinfo = &(desc->rx_ctxt->sring_buf_info);
+	while (rinfo != NULL) {
+		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;
+		}
+		next = le32_to_cpu(rinfo->next);
+		rinfo = next ? (struct ring_buffer_info *)TO_APPS_ADDR(next) : NULL;
+	}
+
+	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 btcomqipc_firmware_load(struct qcom_btss *desc)
+{
+	const char *dot = strrchr(desc->firmware, '.');
+	const struct elf32_phdr *phdrs;
+	const struct firmware *seg_fw;
+	const struct elf32_phdr *phdr;
+	const struct elf32_hdr *ehdr;
+	const struct firmware *fw;
+	size_t metadata_len;
+	size_t stem_len;
+	void *metadata;
+	char *seg_name;
+	int i, ret;
+
+	if (!dot || dot != desc->firmware + strlen(desc->firmware) - 4) {
+		dev_err(desc->dev, "Invalid firmware name: %s\n",
+			desc->firmware);
+		return -EINVAL;
+	}
+
+	stem_len = dot - desc->firmware;
+
+	ret = request_firmware(&fw, desc->firmware, desc->dev);
+	if (ret) {
+		dev_err(desc->dev, "Failed to request firmware: %d\n",
+			ret);
+		return ret;
+	}
+
+	metadata = qcom_mdt_read_metadata(fw, &metadata_len, desc->firmware, desc->dev);
+	if (IS_ERR(metadata)) {
+		ret = PTR_ERR(metadata);
+		dev_err(desc->dev, "error %d reading firmware %s metadata\n",
+			ret, desc->firmware);
+		goto release_fw;
+	}
+
+	ret = qcom_scm_pas_init_image(BTSS_PAS_ID, metadata, metadata_len, NULL);
+	kfree(metadata);
+	if (ret) {
+		dev_err(desc->dev, "error %d initializing firmware %s\n",
+			ret, desc->firmware);
+		goto release_fw;
+	}
+
+	/*
+	 * qcom_mdt_read_metadata has already validated the ELF/mdt header, so
+	 * no need to revalidate it again here.
+	 */
+	ehdr = (const struct elf32_hdr *)fw->data;
+	phdrs = (const struct elf32_phdr *)(ehdr + 1);
+
+	/* guard against a large (incorrect) number of program headers */
+	if (ehdr->e_phnum > 99) {
+		dev_err(desc->dev, "Too many program headers: %d\n",
+			ehdr->e_phnum);
+		ret = -EINVAL;
+		goto release_fw;
+	}
+
+	for (i = 0; i < ehdr->e_phnum; i++) {
+		phdr = &phdrs[i];
+
+		/* Only process valid loadable data segments */
+		if (phdr->p_type != PT_LOAD || !phdr->p_memsz)
+			continue;
+
+		if (phdr->p_vaddr > desc->mem_size ||
+		    phdr->p_filesz > desc->mem_size - phdr->p_vaddr) {
+			dev_err(desc->dev,
+				"Segment data exceeds the reserved memory area!\n");
+			goto release_fw;
+		}
+
+		/* Check if firmware is split across multiple segment files */
+		if (phdr->p_offset > fw->size ||
+		    phdr->p_filesz > fw->size - phdr->p_offset) {
+			seg_name = kasprintf(GFP_KERNEL, "%.*sb%02d",
+					     (int)stem_len, desc->firmware, i);
+			if (!seg_name) {
+				ret = -ENOMEM;
+				goto release_fw;
+			}
+
+			ret = request_firmware(&seg_fw, seg_name,
+					       desc->dev);
+			if (ret)
+				dev_err(desc->dev,
+					"Could not find split segment binary: %s\n",
+					seg_name);
+
+			/*
+			 * 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);
+			kfree(seg_name);
+			seg_name = NULL;
+
+			if (ret)
+				goto release_fw;
+		} else {
+			memcpy_toio(desc->mem_region + phdr->p_vaddr,
+				    fw->data + phdr->p_offset, phdr->p_filesz);
+		}
+	}
+
+release_fw:
+	release_firmware(fw);
+	return ret;
+}
+
+static int btqcomipc_open(struct hci_dev *hdev)
+{
+	struct qcom_btss *desc = hci_get_drvdata(hdev);
+	int ret;
+
+	if (!qcom_scm_pas_supported(BTSS_PAS_ID)) {
+		bt_dev_err(hdev,
+			   "PAS is not available for peripheral: 0x%x",
+			   BTSS_PAS_ID);
+		return -ENODEV;
+	}
+
+	ret = btcomqipc_firmware_load(desc);
+	if (ret) {
+		bt_dev_err(hdev, "Failed to load firmware: %d", ret);
+		return ret;
+	}
+
+	/* Boot firmware */
+	ret = qcom_scm_pas_auth_and_reset(BTSS_PAS_ID);
+	if (ret) {
+		bt_dev_err(hdev, "Failed to boot firmware: %d", ret);
+		return ret;
+	}
+
+	msleep(BTSS_PWR_CTRL_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");
+		ret = -ETIMEDOUT;
+		goto shutdown;
+	}
+
+	return 0;
+
+shutdown:
+	qcom_scm_pas_shutdown(BTSS_PAS_ID);
+	return ret;
+}
+
+static int btqcomipc_close(struct hci_dev *hdev)
+{
+	int ret;
+
+	/*
+	 * Trigger the controller to shutdown. Once the FW has completed its
+	 * shutdown sequence, it will send an IPC_CMD_STOP message to the host
+	 * upon which the desc->running flag is set to false.
+	 */
+	ret = qcom_scm_pas_shutdown(BTSS_PAS_ID);
+	if (ret) {
+		bt_dev_err(hdev, "Failed to stop firmware: %d", ret);
+		return ret;
+	}
+
+	msleep(BTSS_PWR_CTRL_DELAY_MS);
+
+	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;
+	}
+
+	/* 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);
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&desc->lock, flags);
+
+	if (unlikely(!READ_ONCE(desc->running))) {
+		spin_unlock_irqrestore(&desc->lock, flags);
+		bt_dev_err(hdev,
+			   "BTSS not initialized, failed to send message");
+		ret = -ENODEV;
+		goto free_skb;
+	}
+
+	ret = btss_send(desc, hdr, skb);
+	spin_unlock_irqrestore(&desc->lock, flags);
+	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_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 = devm_alloc_ordered_workqueue(dev, "btss_wq", 0);
+	if (!desc->wq) {
+		dev_err(dev, "Failed to initialize workqueue\n");
+		return -ENOMEM;
+	}
+
+	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_deinit(struct qcom_btss *desc)
+{
+	disable_irq(desc->irq);
+	if (desc->wq != NULL) {
+		flush_workqueue(desc->wq);
+		skb_queue_purge(&desc->tx_q);
+	}
+}
+
+static int btqcomipc_alloc_memory_region(struct qcom_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 btqcomipc_probe(struct platform_device *pdev)
+{
+	struct reset_control *btss_reset;
+	struct device *dev = &pdev->dev;
+	struct qcom_btss *desc;
+	struct hci_dev *hdev;
+	struct of_phandle_args args;
+	struct device_node *ipc_np;
+	struct clk *lpo_clk;
+	int ret;
+
+	desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+	if (!desc)
+		return -ENOMEM;
+
+	desc->dev = dev;
+
+	ret = of_property_read_string(dev->of_node, "firmware-name",
+				      &desc->firmware);
+	if (ret < 0)
+		return ret;
+
+	ret = btqcomipc_alloc_memory_region(desc);
+	if (ret)
+		return ret;
+
+	ret = of_parse_phandle_with_fixed_args(dev->of_node, "qcom,ipc", 2, 0,
+					       &args);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get ipc node\n");
+
+	ipc_np = args.np;
+
+	desc->regmap = device_node_to_regmap(ipc_np);
+	if (IS_ERR(desc->regmap)) {
+		dev_err(dev, "Failed to get regmap from ipc node\n");
+		of_node_put(ipc_np);
+		return PTR_ERR(desc->regmap);
+	}
+
+	desc->offset = args.args[0];
+	desc->bit = args.args[1];
+
+	of_node_put(ipc_np);
+
+	lpo_clk = devm_clk_get_enabled(dev, "lpo");
+	if (IS_ERR(lpo_clk))
+		return dev_err_probe(dev, PTR_ERR(lpo_clk),
+				     "Failed to get lpo clock\n");
+
+	btss_reset = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+	if (IS_ERR_OR_NULL(btss_reset))
+		return dev_err_probe(dev, PTR_ERR(btss_reset),
+				     "unable to deassert reset\n");
+
+	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->setup = btqcomipc_setup;
+	hdev->send = btqcomipc_send;
+	hdev->flush = btqcomipc_flush;
+	hdev->set_bdaddr = qca_set_bdaddr;
+
+	ret = btqcomipc_init(desc);
+	if (ret) {
+		hci_free_dev(hdev);
+		return dev_err_probe(dev, ret, "Failed to initialize\n");
+	}
+
+	ret = hci_register_dev(hdev);
+	if (ret < 0) {
+		btqcomipc_deinit(desc);
+		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->hdev) {
+		hci_unregister_dev(desc->hdev);
+		hci_free_dev(desc->hdev);
+		desc->hdev = NULL;
+	}
+
+	btqcomipc_deinit(desc);
+}
+
+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	[flat|nested] 26+ messages in thread

* [PATCH v4 4/6] Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
@ 2026-07-06 17:26   ` George Moussalem
  0 siblings, 0 replies; 26+ messages in thread
From: George Moussalem @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem

Add support for the Bluetooth controller found in the IPQ5018 SoC.
This driver implements firmware loading and the transport layer between
the HCI core and the Bluetooth controller.

The firmware is loaded by the host into the 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 controller.

An outgoing APCS IPC bit and an incoming GIC interrupt handle host/guest
signaling.

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

diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 4e8c24d757e9..a2d82f27f07a 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -413,6 +413,20 @@ 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"
+	depends on OF && ARCH_QCOM
+	select BT_QCA
+	select QCOM_MDT_LOADER
+	select QCOM_SCM
+	help
+	  Qualcomm IPQ5018 IPC based HCI driver.
+	  This driver is used to load firmware and bridge HCI data onto shared
+	  memory between the host and the Bluetooth controller.
+
+	  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..bea3b83d9f2e
--- /dev/null
+++ b/drivers/bluetooth/btqcomipc.c
@@ -0,0 +1,1148 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/elf.h>
+#include <linux/firmware.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/mdt_loader.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 by IPC interrupt is required by firmware.
+ *
+ * - 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	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 BTSS_PAS_ID		0xc
+
+#define BTSS_PWR_CTRL_DELAY_MS	50
+
+struct long_msg_info {
+	u16 smsg_free_cnt;
+	u16 lmsg_free_cnt;
+	u8 ridx;
+	u8 widx;
+} __packed;
+
+struct ipc_aux_ptr {
+	u32 len;
+	u32 buf;
+};
+
+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 hci_dev *hdev;
+
+	struct regmap *regmap;
+	u32 offset;
+	u32 bit;
+	int irq;
+
+	const char *firmware;
+	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 inline u32 btss_lbuf_size(struct qcom_btss *desc,
+				 const struct context_info *ctxt)
+{
+	u32 total_size = le16_to_cpu(ctxt->total_size);
+	u32 lring_buf = le32_to_cpu(ctxt->lring_buf);
+
+	return ((TO_BT_ADDR((void *)ctxt) + total_size) -
+		lring_buf) / ctxt->lmsg_buf_cnt;
+}
+
+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 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 = btss_lbuf_size(desc, desc->tx_ctxt);
+	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(le32_to_cpu(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)
+{
+	struct ring_buffer_info *rinfo;
+	u32 next;
+	u8 idx;
+
+	rinfo = &(desc->tx_ctxt->sring_buf_info);
+	while (rinfo != NULL) {
+		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;
+		}
+
+		next = le32_to_cpu(rinfo->next);
+		rinfo = next ? (struct ring_buffer_info *)TO_APPS_ADDR(next) : NULL;
+	}
+
+	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 ipc_aux_ptr aux_ptr = { };
+	struct ring_buffer_info *rinfo;
+	struct ring_buffer *rbuf;
+	bool is_lbuf_full = false;
+	bool is_sbuf_full = false;
+	u16 hdr = msg_hdr;
+	u16 lmsg_free_cnt;
+	void *ptr_buf;
+	u8 lmsg_widx;
+	u32 len;
+
+	/* Account for HCI packet type as it's not included in the skb payload */
+	len = skb ? skb->len + 1 : 0;
+
+	if (len > IPC_MSG_PLD_SZ) {
+		hdr |= IPC_HDR_LONG_MSG;
+
+		/* snapshot of long message context in case we need to reverse */
+		lmsg_free_cnt = le16_to_cpu(desc->lmsg_ctxt.lmsg_free_cnt);
+		lmsg_widx = desc->lmsg_ctxt.widx;
+
+		ptr_buf = btss_alloc_lmsg(desc, len,
+					  &aux_ptr, &is_lbuf_full);
+		if (IS_ERR(ptr_buf)) {
+			bt_dev_err(hdev, "TX long buffers 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, "TX short buffers full");
+		hdev->stat.err_tx++;
+		if (len > IPC_MSG_PLD_SZ) {
+			/* reverse long message context if we allocated one */
+			desc->lmsg_ctxt.lmsg_free_cnt = cpu_to_le16(lmsg_free_cnt);
+			desc->lmsg_ctxt.widx = lmsg_widx;
+		}
+		return PTR_ERR(rinfo);
+	}
+
+	rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(le32_to_cpu(rinfo->rbuf))))[rinfo->widx];
+
+	if (!skb)
+		goto complete_tx;
+
+	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 free buffer count is low, send ACK request to signal to the
+	 * firmware to process and free up queued buffers in the TX ring.
+	 */
+	if (is_sbuf_full || is_lbuf_full)
+		hdr |= IPC_HDR_REQ_ACK;
+
+complete_tx:
+	rbuf->msg_hdr = cpu_to_le16(hdr);
+	rbuf->len = cpu_to_le16(len);
+
+	dma_wmb();
+
+	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 = btss_lbuf_size(desc, desc->tx_ctxt);
+
+	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_recv_cust_frame(struct qcom_btss *desc, u8 cmd)
+{
+	u16 msg_hdr = 0;
+	int ret;
+
+	switch (cmd) {
+	case IPC_CMD_STOP:
+		spin_unlock(&desc->lock);
+		ret = qcom_scm_pas_set_bluetooth_power_mode(BTSS_PAS_ID, true);
+		spin_lock(&desc->lock);
+		if (ret && ret != -EOPNOTSUPP) {
+			bt_dev_err(desc->hdev,
+				   "Failed to apply BTSS power-save mode: %d",
+				   ret);
+			return ret;
+		}
+
+		WRITE_ONCE(desc->running, false);
+
+		msg_hdr |= cmd;
+		ret = btss_send(desc, msg_hdr, NULL);
+		if (ret)
+			bt_dev_err(desc->hdev,
+				   "Failed to send control message");
+		break;
+	case IPC_CMD_START:
+		spin_unlock(&desc->lock);
+		ret = qcom_scm_pas_set_bluetooth_power_mode(BTSS_PAS_ID, false);
+		spin_lock(&desc->lock);
+		if (ret && ret != -EOPNOTSUPP) {
+			bt_dev_err(desc->hdev,
+				   "Failed to apply BTSS active power mode: %d",
+				   ret);
+			return ret;
+		}
+
+		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);
+		break;
+	default:
+		bt_dev_err(desc->hdev, "Unsupported CMD ID: %u", cmd);
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+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 < 1 + 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 < 1 + HCI_COMMAND_HDR_SIZE)
+			return -EILSEQ;
+		struct hci_command_hdr *hdr = (struct hci_command_hdr *)(data + 1);
+
+		pkt_len = HCI_COMMAND_HDR_SIZE + hdr->plen;
+		break;
+	}
+	case HCI_ACLDATA_PKT:
+	{
+		if (len < 1 + 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 < 1 + 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 + 1 > 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;
+	u16 msg_hdr;
+	u16 msg_len;
+	u32 lsz;
+	int ret;
+
+	ridx = rinfo->ridx;
+
+	while (ridx != rinfo->widx) {
+		rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(le32_to_cpu(rinfo->rbuf))))[ridx];
+		msg_hdr = le16_to_cpu(rbuf->msg_hdr);
+		msg_len = le16_to_cpu(rbuf->len);
+
+		if (!(msg_hdr & IPC_HDR_LONG_MSG) && msg_len > IPC_MSG_PLD_SZ) {
+			desc->hdev->stat.err_rx++;
+			bt_dev_err(desc->hdev,
+				   "Dropping short RX frame with invalid len %u",
+				   msg_len);
+			ridx = (ridx + 1) % rinfo->ring_buf_cnt;
+			rinfo->ridx = ridx;
+			if (rx_count)
+				(*rx_count)++;
+			continue;
+		}
+
+		if (msg_hdr & IPC_HDR_LONG_MSG) {
+			rxbuf = TO_APPS_ADDR(le32_to_cpu(rbuf->payload.lmsg_data));
+			lsz = btss_lbuf_size(desc, desc->rx_ctxt);
+
+			if (IS_RX_MEM_NON_CONTIGIOUS(le32_to_cpu(rbuf->payload.lmsg_data),
+					     msg_len, lsz)) {
+				lbuf_idx = GET_RX_INDEX_FROM_BUF(
+						le32_to_cpu(rbuf->payload.lmsg_data), lsz);
+
+				blks_consumed = desc->rx_ctxt->lmsg_buf_cnt -
+					lbuf_idx;
+				aux_ptr.len = msg_len - (blks_consumed * lsz);
+				aux_ptr.buf = le32_to_cpu(desc->rx_ctxt->lring_buf);
+			}
+		} else {
+			rxbuf = rbuf->payload.smsg_data;
+		}
+
+		*ack = msg_hdr & IPC_HDR_REQ_ACK;
+
+		pkt_type = FIELD_GET(IPC_HDR_PKT_TYPE_MASK, msg_hdr);
+
+		switch (pkt_type) {
+		case IPC_HDR_PKT_TYPE_HCI:
+			buf = kmalloc(msg_len, GFP_ATOMIC);
+			if (!buf) {
+				desc->hdev->stat.err_rx++;
+				bt_dev_err(desc->hdev,
+					   "Dropping HCI frame due to RX allocation failure");
+				ridx = (ridx + 1) % rinfo->ring_buf_cnt;
+				rinfo->ridx = ridx;
+				if (rx_count)
+					(*rx_count)++;
+				continue;
+			}
+
+			memcpy_fromio(buf, rxbuf, msg_len - aux_ptr.len);
+
+			if (aux_ptr.buf)
+				memcpy_fromio(buf + (msg_len - aux_ptr.len),
+					      TO_APPS_ADDR(aux_ptr.buf),
+					      aux_ptr.len);
+
+			ret = btss_recv_hci_frame(desc, buf, msg_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, msg_hdr);
+			ret = btss_recv_cust_frame(desc, cmd);
+			if (ret)
+				bt_dev_warn(desc->hdev,
+					    "Failed to process custom frame: %d",
+					    ret);
+			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;
+	u32 next;
+	u32 lmsg;
+	u16 msg_hdr;
+	u16 msg_len;
+	u8 tidx;
+
+	rinfo = &desc->tx_ctxt->sring_buf_info;
+	while (rinfo != NULL) {
+		tidx = rinfo->tidx;
+		rbuf = (struct ring_buffer *)TO_APPS_ADDR(le32_to_cpu(rinfo->rbuf));
+
+		while (tidx != rinfo->ridx) {
+			msg_hdr = le16_to_cpu(rbuf[tidx].msg_hdr);
+			if (msg_hdr & IPC_HDR_LONG_MSG) {
+				lmsg = le32_to_cpu(rbuf[tidx].payload.lmsg_data);
+				msg_len = le16_to_cpu(rbuf[tidx].len);
+				btss_free_lmsg(desc, lmsg, msg_len);
+			}
+
+			tidx = (tidx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+			desc->lmsg_ctxt.smsg_free_cnt++;
+		}
+
+		rinfo->tidx = tidx;
+
+		btss_process_tx_queue(desc);
+
+		next = le32_to_cpu(rinfo->next);
+		rinfo = next ? (struct ring_buffer_info *)TO_APPS_ADDR(next) : NULL;
+	}
+}
+
+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, next;
+	int ret;
+
+	spin_lock(&desc->lock);
+
+	if (unlikely(!READ_ONCE(desc->running))) {
+		/*
+		 * FW sets offset of RX context info at the start of the memory
+		 * region upon boot
+		 */
+		offset = readl(desc->mem_region);
+		dev_dbg(desc->dev, "offset after firmware boot: 0x%08x\n",
+			offset);
+		desc->rx_ctxt = (struct context_info *)(desc->mem_region + offset);
+	} else {
+		btss_process_ack(desc);
+	}
+
+	rinfo = &(desc->rx_ctxt->sring_buf_info);
+	while (rinfo != NULL) {
+		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;
+		}
+		next = le32_to_cpu(rinfo->next);
+		rinfo = next ? (struct ring_buffer_info *)TO_APPS_ADDR(next) : NULL;
+	}
+
+	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 btcomqipc_firmware_load(struct qcom_btss *desc)
+{
+	const char *dot = strrchr(desc->firmware, '.');
+	const struct elf32_phdr *phdrs;
+	const struct firmware *seg_fw;
+	const struct elf32_phdr *phdr;
+	const struct elf32_hdr *ehdr;
+	const struct firmware *fw;
+	size_t metadata_len;
+	size_t stem_len;
+	void *metadata;
+	char *seg_name;
+	int i, ret;
+
+	if (!dot || dot != desc->firmware + strlen(desc->firmware) - 4) {
+		dev_err(desc->dev, "Invalid firmware name: %s\n",
+			desc->firmware);
+		return -EINVAL;
+	}
+
+	stem_len = dot - desc->firmware;
+
+	ret = request_firmware(&fw, desc->firmware, desc->dev);
+	if (ret) {
+		dev_err(desc->dev, "Failed to request firmware: %d\n",
+			ret);
+		return ret;
+	}
+
+	metadata = qcom_mdt_read_metadata(fw, &metadata_len, desc->firmware, desc->dev);
+	if (IS_ERR(metadata)) {
+		ret = PTR_ERR(metadata);
+		dev_err(desc->dev, "error %d reading firmware %s metadata\n",
+			ret, desc->firmware);
+		goto release_fw;
+	}
+
+	ret = qcom_scm_pas_init_image(BTSS_PAS_ID, metadata, metadata_len, NULL);
+	kfree(metadata);
+	if (ret) {
+		dev_err(desc->dev, "error %d initializing firmware %s\n",
+			ret, desc->firmware);
+		goto release_fw;
+	}
+
+	/*
+	 * qcom_mdt_read_metadata has already validated the ELF/mdt header, so
+	 * no need to revalidate it again here.
+	 */
+	ehdr = (const struct elf32_hdr *)fw->data;
+	phdrs = (const struct elf32_phdr *)(ehdr + 1);
+
+	/* guard against a large (incorrect) number of program headers */
+	if (ehdr->e_phnum > 99) {
+		dev_err(desc->dev, "Too many program headers: %d\n",
+			ehdr->e_phnum);
+		ret = -EINVAL;
+		goto release_fw;
+	}
+
+	for (i = 0; i < ehdr->e_phnum; i++) {
+		phdr = &phdrs[i];
+
+		/* Only process valid loadable data segments */
+		if (phdr->p_type != PT_LOAD || !phdr->p_memsz)
+			continue;
+
+		if (phdr->p_vaddr > desc->mem_size ||
+		    phdr->p_filesz > desc->mem_size - phdr->p_vaddr) {
+			dev_err(desc->dev,
+				"Segment data exceeds the reserved memory area!\n");
+			goto release_fw;
+		}
+
+		/* Check if firmware is split across multiple segment files */
+		if (phdr->p_offset > fw->size ||
+		    phdr->p_filesz > fw->size - phdr->p_offset) {
+			seg_name = kasprintf(GFP_KERNEL, "%.*sb%02d",
+					     (int)stem_len, desc->firmware, i);
+			if (!seg_name) {
+				ret = -ENOMEM;
+				goto release_fw;
+			}
+
+			ret = request_firmware(&seg_fw, seg_name,
+					       desc->dev);
+			if (ret)
+				dev_err(desc->dev,
+					"Could not find split segment binary: %s\n",
+					seg_name);
+
+			/*
+			 * 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);
+			kfree(seg_name);
+			seg_name = NULL;
+
+			if (ret)
+				goto release_fw;
+		} else {
+			memcpy_toio(desc->mem_region + phdr->p_vaddr,
+				    fw->data + phdr->p_offset, phdr->p_filesz);
+		}
+	}
+
+release_fw:
+	release_firmware(fw);
+	return ret;
+}
+
+static int btqcomipc_open(struct hci_dev *hdev)
+{
+	struct qcom_btss *desc = hci_get_drvdata(hdev);
+	int ret;
+
+	if (!qcom_scm_pas_supported(BTSS_PAS_ID)) {
+		bt_dev_err(hdev,
+			   "PAS is not available for peripheral: 0x%x",
+			   BTSS_PAS_ID);
+		return -ENODEV;
+	}
+
+	ret = btcomqipc_firmware_load(desc);
+	if (ret) {
+		bt_dev_err(hdev, "Failed to load firmware: %d", ret);
+		return ret;
+	}
+
+	/* Boot firmware */
+	ret = qcom_scm_pas_auth_and_reset(BTSS_PAS_ID);
+	if (ret) {
+		bt_dev_err(hdev, "Failed to boot firmware: %d", ret);
+		return ret;
+	}
+
+	msleep(BTSS_PWR_CTRL_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");
+		ret = -ETIMEDOUT;
+		goto shutdown;
+	}
+
+	return 0;
+
+shutdown:
+	qcom_scm_pas_shutdown(BTSS_PAS_ID);
+	return ret;
+}
+
+static int btqcomipc_close(struct hci_dev *hdev)
+{
+	int ret;
+
+	/*
+	 * Trigger the controller to shutdown. Once the FW has completed its
+	 * shutdown sequence, it will send an IPC_CMD_STOP message to the host
+	 * upon which the desc->running flag is set to false.
+	 */
+	ret = qcom_scm_pas_shutdown(BTSS_PAS_ID);
+	if (ret) {
+		bt_dev_err(hdev, "Failed to stop firmware: %d", ret);
+		return ret;
+	}
+
+	msleep(BTSS_PWR_CTRL_DELAY_MS);
+
+	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;
+	}
+
+	/* 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);
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&desc->lock, flags);
+
+	if (unlikely(!READ_ONCE(desc->running))) {
+		spin_unlock_irqrestore(&desc->lock, flags);
+		bt_dev_err(hdev,
+			   "BTSS not initialized, failed to send message");
+		ret = -ENODEV;
+		goto free_skb;
+	}
+
+	ret = btss_send(desc, hdr, skb);
+	spin_unlock_irqrestore(&desc->lock, flags);
+	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_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 = devm_alloc_ordered_workqueue(dev, "btss_wq", 0);
+	if (!desc->wq) {
+		dev_err(dev, "Failed to initialize workqueue\n");
+		return -ENOMEM;
+	}
+
+	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_deinit(struct qcom_btss *desc)
+{
+	disable_irq(desc->irq);
+	if (desc->wq != NULL) {
+		flush_workqueue(desc->wq);
+		skb_queue_purge(&desc->tx_q);
+	}
+}
+
+static int btqcomipc_alloc_memory_region(struct qcom_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 btqcomipc_probe(struct platform_device *pdev)
+{
+	struct reset_control *btss_reset;
+	struct device *dev = &pdev->dev;
+	struct qcom_btss *desc;
+	struct hci_dev *hdev;
+	struct of_phandle_args args;
+	struct device_node *ipc_np;
+	struct clk *lpo_clk;
+	int ret;
+
+	desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+	if (!desc)
+		return -ENOMEM;
+
+	desc->dev = dev;
+
+	ret = of_property_read_string(dev->of_node, "firmware-name",
+				      &desc->firmware);
+	if (ret < 0)
+		return ret;
+
+	ret = btqcomipc_alloc_memory_region(desc);
+	if (ret)
+		return ret;
+
+	ret = of_parse_phandle_with_fixed_args(dev->of_node, "qcom,ipc", 2, 0,
+					       &args);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get ipc node\n");
+
+	ipc_np = args.np;
+
+	desc->regmap = device_node_to_regmap(ipc_np);
+	if (IS_ERR(desc->regmap)) {
+		dev_err(dev, "Failed to get regmap from ipc node\n");
+		of_node_put(ipc_np);
+		return PTR_ERR(desc->regmap);
+	}
+
+	desc->offset = args.args[0];
+	desc->bit = args.args[1];
+
+	of_node_put(ipc_np);
+
+	lpo_clk = devm_clk_get_enabled(dev, "lpo");
+	if (IS_ERR(lpo_clk))
+		return dev_err_probe(dev, PTR_ERR(lpo_clk),
+				     "Failed to get lpo clock\n");
+
+	btss_reset = devm_reset_control_get_exclusive_deasserted(dev, NULL);
+	if (IS_ERR_OR_NULL(btss_reset))
+		return dev_err_probe(dev, PTR_ERR(btss_reset),
+				     "unable to deassert reset\n");
+
+	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->setup = btqcomipc_setup;
+	hdev->send = btqcomipc_send;
+	hdev->flush = btqcomipc_flush;
+	hdev->set_bdaddr = qca_set_bdaddr;
+
+	ret = btqcomipc_init(desc);
+	if (ret) {
+		hci_free_dev(hdev);
+		return dev_err_probe(dev, ret, "Failed to initialize\n");
+	}
+
+	ret = hci_register_dev(hdev);
+	if (ret < 0) {
+		btqcomipc_deinit(desc);
+		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->hdev) {
+		hci_unregister_dev(desc->hdev);
+		hci_free_dev(desc->hdev);
+		desc->hdev = NULL;
+	}
+
+	btqcomipc_deinit(desc);
+}
+
+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	[flat|nested] 26+ messages in thread

* [PATCH v4 5/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
  2026-07-06 17:26 ` George Moussalem
@ 2026-07-06 17:26   ` George Moussalem
  -1 siblings, 0 replies; 26+ messages in thread
From: George Moussalem via B4 Relay @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Bartosz Golaszewski

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

Add nodes for the reserved memory carveout and Bluetooth.

Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 arch/arm64/boot/dts/qcom/ipq5018.dtsi | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/ipq5018.dtsi b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
index 6f8004a22a1f..c97c20d4428c 100644
--- a/arch/arm64/boot/dts/qcom/ipq5018.dtsi
+++ b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
@@ -17,6 +17,23 @@ / {
 	#address-cells = <2>;
 	#size-cells = <2>;
 
+	bluetooth: bluetooth {
+		compatible = "qcom,ipq5018-bt";
+
+		firmware-name = "qca/bt_fw_patch.mbn";
+
+		clocks = <&gcc GCC_BTSS_LPO_CLK>;
+		clock-names = "lpo";
+		resets = <&gcc GCC_BTSS_BCR>;
+
+		qcom,ipc = <&apcs_glb 8 23>;
+		interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+		memory-region = <&btss_region>;
+
+		status = "disabled";
+	};
+
 	clocks {
 		gephy_rx_clk: gephy-rx-clk {
 			compatible = "fixed-clock";
@@ -136,6 +153,11 @@ reserved-memory {
 		#size-cells = <2>;
 		ranges;
 
+		btss_region: bluetooth@7000000 {
+			reg = <0x0 0x07000000 0x0 0x58000>;
+			no-map;
+		};
+
 		bootloader@4a800000 {
 			reg = <0x0 0x4a800000 0x0 0x200000>;
 			no-map;

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v4 5/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
@ 2026-07-06 17:26   ` George Moussalem
  0 siblings, 0 replies; 26+ messages in thread
From: George Moussalem @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Bartosz Golaszewski

Add nodes for the reserved memory carveout and Bluetooth.

Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 arch/arm64/boot/dts/qcom/ipq5018.dtsi | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/ipq5018.dtsi b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
index 6f8004a22a1f..c97c20d4428c 100644
--- a/arch/arm64/boot/dts/qcom/ipq5018.dtsi
+++ b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
@@ -17,6 +17,23 @@ / {
 	#address-cells = <2>;
 	#size-cells = <2>;
 
+	bluetooth: bluetooth {
+		compatible = "qcom,ipq5018-bt";
+
+		firmware-name = "qca/bt_fw_patch.mbn";
+
+		clocks = <&gcc GCC_BTSS_LPO_CLK>;
+		clock-names = "lpo";
+		resets = <&gcc GCC_BTSS_BCR>;
+
+		qcom,ipc = <&apcs_glb 8 23>;
+		interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+		memory-region = <&btss_region>;
+
+		status = "disabled";
+	};
+
 	clocks {
 		gephy_rx_clk: gephy-rx-clk {
 			compatible = "fixed-clock";
@@ -136,6 +153,11 @@ reserved-memory {
 		#size-cells = <2>;
 		ranges;
 
+		btss_region: bluetooth@7000000 {
+			reg = <0x0 0x07000000 0x0 0x58000>;
+			no-map;
+		};
+
 		bootloader@4a800000 {
 			reg = <0x0 0x4a800000 0x0 0x200000>;
 			no-map;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v4 6/6] MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver
  2026-07-06 17:26 ` George Moussalem
@ 2026-07-06 17:26   ` George Moussalem
  -1 siblings, 0 replies; 26+ messages in thread
From: George Moussalem via B4 Relay @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Bartosz Golaszewski

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

Add maintainers entry for Qualcomm IPQ5018 Bluetooth driver.

Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 0b9d7c8276ac..60f7251d1a16 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22289,6 +22289,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/regulator/vqmmc-ipq4019-regulator.yaml
 F:	drivers/regulator/vqmmc-ipq4019-regulator.c
 
+QUALCOMM IPQ5018 BLUETOOTH DRIVER
+M:	George Moussalem <george.moussalem@outlook.com>
+L:	linux-bluetooth@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
+F:	drivers/bluetooth/btqcomipc.c
+
 QUALCOMM IRIS VIDEO ACCELERATOR DRIVER
 M:	Vikash Garodia <vikash.garodia@oss.qualcomm.com>
 M:	Dikshita Agarwal <dikshita.agarwal@oss.qualcomm.com>

-- 
2.53.0



^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v4 6/6] MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver
@ 2026-07-06 17:26   ` George Moussalem
  0 siblings, 0 replies; 26+ messages in thread
From: George Moussalem @ 2026-07-06 17:26 UTC (permalink / raw)
  To: Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
	Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	George Moussalem, Bartosz Golaszewski

Add maintainers entry for Qualcomm IPQ5018 Bluetooth driver.

Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 0b9d7c8276ac..60f7251d1a16 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22289,6 +22289,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/regulator/vqmmc-ipq4019-regulator.yaml
 F:	drivers/regulator/vqmmc-ipq4019-regulator.c
 
+QUALCOMM IPQ5018 BLUETOOTH DRIVER
+M:	George Moussalem <george.moussalem@outlook.com>
+L:	linux-bluetooth@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
+F:	drivers/bluetooth/btqcomipc.c
+
 QUALCOMM IRIS VIDEO ACCELERATOR DRIVER
 M:	Vikash Garodia <vikash.garodia@oss.qualcomm.com>
 M:	Dikshita Agarwal <dikshita.agarwal@oss.qualcomm.com>

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 3/6] firmware: qcom: scm: Add support for setting Bluetooth power modes
  2026-07-06 17:26   ` George Moussalem
  (?)
@ 2026-07-06 17:36   ` sashiko-bot
  -1 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-07-06 17:36 UTC (permalink / raw)
  To: George Moussalem; +Cc: robh, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] TrustZone ABI Command ID collision between QCOM_SCM_PIL_PAS_BT_PWR_MODE and QCOM_SCM_PIL_PAS_GET_RSCTABLE.
--

commit 3517e2228bae4426b6fd48e30229414c0c97a8c2
Author: George Moussalem <george.moussalem@outlook.com>

firmware: qcom: scm: Add support for setting Bluetooth power modes

The Bluetooth subsystem (BTSS) on the IPQ5018 SoC supports setting power
modes which are required to be configured through a Secure Channel
Manager (SCM) call to TrustZone. However, not all Trusted Execution
Environment (QSEE) images support this call, so first check if the call
is available.

> diff --git a/drivers/firmware/qcom/qcom_scm.h b/drivers/firmware/qcom/qcom_scm.h
> index caab80a73e17f..5579df5a2aca7 100644
> --- a/drivers/firmware/qcom/qcom_scm.h
> +++ b/drivers/firmware/qcom/qcom_scm.h
> @@ -105,6 +105,7 @@ int qcom_scm_shm_bridge_enable(struct device *scm_dev);
>  #define QCOM_SCM_PIL_PAS_SHUTDOWN	0x06
>  #define QCOM_SCM_PIL_PAS_IS_SUPPORTED	0x07
>  #define QCOM_SCM_PIL_PAS_MSS_RESET	0x0a
> +#define QCOM_SCM_PIL_PAS_BT_PWR_MODE	0x21
>  #define QCOM_SCM_PIL_PAS_GET_RSCTABLE	0x21

[Severity: High]
Does this command ID definition collide with the existing
QCOM_SCM_PIL_PAS_GET_RSCTABLE?

Because both are defined as 0x21, __qcom_scm_is_call_available() will return
true on any SoC supporting GET_RSCTABLE, falsely indicating that BT_PWR_MODE
is supported.

When qcom_scm_pas_set_bluetooth_power_mode() is called, it issues a Secure
Monitor Call with 2 arguments. However, TrustZone will route this to the
GET_RSCTABLE handler, which expects 5 arguments, including physical memory
addresses.

Since the trailing arguments in the descriptor are implicitly zero-initialized,
could this cause TrustZone to attempt reading or writing at physical address
0x0, leading to memory corruption or a fatal Secure World exception?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260706-ipq5018-bluetooth-v4-0-350262a30959@outlook.com?part=3

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 4/6] Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
  2026-07-06 17:26   ` George Moussalem
  (?)
@ 2026-07-06 17:42   ` sashiko-bot
  -1 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-07-06 17:42 UTC (permalink / raw)
  To: George Moussalem; +Cc: robh, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [Critical] Unchecked failure of `request_firmware()` leads to a guaranteed NULL pointer dereference.
- [High] Missing bounds check on split firmware segment size allows out-of-bounds memory reads.
- [High] Use-After-Free of `hci_dev` during module removal due to incorrect teardown order.
- [High] The `desc->running` flag is never reset on shutdown, breaking subsequent device opens.
- [High] Race condition in `btqcomipc_send` strands packets in the TX queue indefinitely.
- [High] Endianness bugs in ring buffer macros cause memory corruption on big-endian architectures.
- [High] Missing validation of shared memory values allows division-by-zero and heap overflow.
- [High] Spurious interrupt before firmware boot causes garbage memory dereference.
- [High] Invalid direct dereference of packed structs mapped as I/O memory.
- [Medium] Missing read memory barrier (`dma_rmb`) in RX ring buffer processing.
--

commit b919a0d9e5de552fc0f99dc627213c500aff5dac
Author: George Moussalem <george.moussalem@outlook.com>
Date:   Mon Jul 6 17:26:20 2026 +0000

    Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
    
    Add support for the Bluetooth controller found in the IPQ5018 SoC.
    This driver implements firmware loading and the transport layer between
    the HCI core and the Bluetooth controller.

> diff --git a/drivers/bluetooth/btqcomipc.c b/drivers/bluetooth/btqcomipc.c
> new file mode 100644
> index 0000000000000..bea3b83d9f2e1
> --- /dev/null
> +++ b/drivers/bluetooth/btqcomipc.c
[ ... ]
> +#define IS_RX_MEM_NON_CONTIGIOUS(buf, len, sz)		\
> +	((buf + len) > (desc->rx_ctxt->lring_buf +	\
> +	(sz * desc->rx_ctxt->lmsg_buf_cnt)))

[Severity: High]
Does this perform native host arithmetic on a little-endian value?

Looking at struct context_info, lring_buf is defined as __le32, but it
is being used in macros like IS_RX_MEM_NON_CONTIGIOUS and
GET_RX_INDEX_FROM_BUF without a le32_to_cpu() conversion.

Could this cause memory corruption or incorrect bounds checking on
big-endian architectures?

[ ... ]
> +static inline u32 btss_lbuf_size(struct qcom_btss *desc,
> +				 const struct context_info *ctxt)
> +{
> +	u32 total_size = le16_to_cpu(ctxt->total_size);
> +	u32 lring_buf = le32_to_cpu(ctxt->lring_buf);
> +
> +	return ((TO_BT_ADDR((void *)ctxt) + total_size) -
> +		lring_buf) / ctxt->lmsg_buf_cnt;
> +}

[Severity: High]
Is it possible for ctxt->lmsg_buf_cnt to be zero here?

Since this value is read directly from shared memory, a buggy or
compromised firmware providing a zero value would cause a division by zero
panic.

Could we add validation for these shared memory values before using them?

[ ... ]
> +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);
> +	unsigned long flags;
> +	int ret;
> +
> +	spin_lock_irqsave(&desc->lock, flags);
> +
> +	if (unlikely(!READ_ONCE(desc->running))) {
> +		spin_unlock_irqrestore(&desc->lock, flags);
> +		bt_dev_err(hdev,
> +			   "BTSS not initialized, failed to send message");
> +		ret = -ENODEV;
> +		goto free_skb;
> +	}
> +
> +	ret = btss_send(desc, hdr, skb);
> +	spin_unlock_irqrestore(&desc->lock, flags);
> +	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;
> +			}

[Severity: High]
Can this sequence strand packets in the TX queue indefinitely?

If btss_send() returns -EAGAIN, the driver releases desc->lock and then
queues the SKB via skb_queue_tail().

If a hardware ACK interrupt fires between the lock release and the queue
enqueue, btss_worker() could acquire the lock, free space in the ring
buffer, and call btss_process_tx_queue() while the queue still appears
empty.

Would it be safer to enqueue the packet before releasing the lock?

[ ... ]
> +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;
> +	u16 msg_hdr;
> +	u16 msg_len;
> +	u32 lsz;
> +	int ret;
> +
> +	ridx = rinfo->ridx;
> +
> +	while (ridx != rinfo->widx) {
> +		rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(le32_to_cpu(rinfo->rbuf))))[ridx];
> +		msg_hdr = le16_to_cpu(rbuf->msg_hdr);

[Severity: Medium]
Is a read memory barrier needed here?

The driver reads the producer index (rinfo->widx) which is updated by
hardware, and then immediately dereferences rbuf->msg_hdr and rbuf->len
from the ring buffer.

Without a dma_rmb() between reading the index and the payload, could
the CPU speculatively fetch stale payload data before the device's write
is guaranteed visible?

[Severity: High]
Does this code safely access I/O memory on ARM64?

The memory region is mapped as Device memory via devm_ioremap(), but
the code casts offsets to struct ring_buffer (which is __packed) and
directly dereferences fields like rbuf->msg_hdr.

Since the compiler can optimize packed struct accesses using unaligned
load instructions, could this trigger synchronous external aborts on
ARM64 systems? Should standard I/O accessors like readw() be used instead?

[ ... ]
> +		if (msg_hdr & IPC_HDR_LONG_MSG) {
> +			rxbuf = TO_APPS_ADDR(le32_to_cpu(rbuf->payload.lmsg_data));
> +			lsz = btss_lbuf_size(desc, desc->rx_ctxt);
> +
> +			if (IS_RX_MEM_NON_CONTIGIOUS(le32_to_cpu(rbuf->payload.lmsg_data),
> +					     msg_len, lsz)) {
> +				lbuf_idx = GET_RX_INDEX_FROM_BUF(
> +						le32_to_cpu(rbuf->payload.lmsg_data), lsz);
> +
> +				blks_consumed = desc->rx_ctxt->lmsg_buf_cnt -
> +					lbuf_idx;
> +				aux_ptr.len = msg_len - (blks_consumed * lsz);

[Severity: High]
Can this subtraction underflow and cause a heap overflow later?

The values from the shared memory context are used to calculate
blks_consumed and then aux_ptr.len. If a compromised firmware provides
values where (blks_consumed * lsz) is greater than msg_len, aux_ptr.len
will wrap to a huge integer.

Could this huge length then cause the subsequent memcpy_fromio() to
overflow the allocated SKB buffer?

[ ... ]
> +static int btcomqipc_firmware_load(struct qcom_btss *desc)
> +{
[ ... ]
> +		/* Check if firmware is split across multiple segment files */
> +		if (phdr->p_offset > fw->size ||
> +		    phdr->p_filesz > fw->size - phdr->p_offset) {
> +			seg_name = kasprintf(GFP_KERNEL, "%.*sb%02d",
> +					     (int)stem_len, desc->firmware, i);
> +			if (!seg_name) {
> +				ret = -ENOMEM;
> +				goto release_fw;
> +			}
> +
> +			ret = request_firmware(&seg_fw, seg_name,
> +					       desc->dev);
> +			if (ret)
> +				dev_err(desc->dev,
> +					"Could not find split segment binary: %s\n",
> +					seg_name);
> +
> +			/*
> +			 * 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);
> +			kfree(seg_name);
> +			seg_name = NULL;
> +
> +			if (ret)
> +				goto release_fw;

[Severity: Critical]
If request_firmware() fails, is seg_fw left uninitialized?

The error is logged with dev_err(), but execution falls through to
memcpy_toio() which will dereference the uninitialized or NULL seg_fw
pointer, causing a kernel panic.

Should the code jump out or abort immediately if request_firmware() fails?

[Severity: High]
Does this memcpy_toio() trust the ELF header size blindly?

It copies phdr->p_filesz bytes from seg_fw->data, but it doesn't verify
that the requested firmware file actually contains that many bytes
(seg_fw->size).

If a truncated or modified firmware file is loaded, could this result in
an out-of-bounds memory read?

[ ... ]
> +static int btqcomipc_close(struct hci_dev *hdev)
> +{
> +	int ret;
> +
> +	/*
> +	 * Trigger the controller to shutdown. Once the FW has completed its
> +	 * shutdown sequence, it will send an IPC_CMD_STOP message to the host
> +	 * upon which the desc->running flag is set to false.
> +	 */
> +	ret = qcom_scm_pas_shutdown(BTSS_PAS_ID);
> +	if (ret) {
> +		bt_dev_err(hdev, "Failed to stop firmware: %d", ret);
> +		return ret;
> +	}
> +
> +	msleep(BTSS_PWR_CTRL_DELAY_MS);
> +
> +	return 0;
> +}

[Severity: High]
Is desc->running reliably reset to false when the device is closed?

The comment mentions that the firmware will send an IPC_CMD_STOP message
which clears desc->running. However, qcom_scm_pas_shutdown() forcefully
halts the peripheral, which might prevent it from sending that final message.

If desc->running remains true, a subsequent call to btqcomipc_open()
will bypass initialization and leave pointers like desc->rx_ctxt in a
stale or invalid state. Should this explicitly reset desc->running?

[ ... ]
> +static int btqcomipc_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 = devm_alloc_ordered_workqueue(dev, "btss_wq", 0);
> +	if (!desc->wq) {
> +		dev_err(dev, "Failed to initialize workqueue\n");
> +		return -ENOMEM;
> +	}
> +
> +	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);

[Severity: High]
Could a spurious interrupt cause a crash before firmware initialization?

The IRQ is enabled here during probe, before the firmware is loaded and
the controller is booted in btqcomipc_open().

If a spurious interrupt fires, btss_worker() will run, see that
desc->running is false, and attempt to read offsets from the uninitialized
desc->mem_region to setup desc->rx_ctxt.

Would it be safer to request the IRQ later, or keep it disabled until
the firmware is actually booted?

[ ... ]
> +static void btqcomipc_remove(struct platform_device *pdev)
> +{
> +	struct qcom_btss *desc = platform_get_drvdata(pdev);
> +
> +	if (desc->hdev) {
> +		hci_unregister_dev(desc->hdev);
> +		hci_free_dev(desc->hdev);
> +		desc->hdev = NULL;
> +	}
> +
> +	btqcomipc_deinit(desc);
> +}

[Severity: High]
Is the HCI device freed before the background workqueue is stopped?

hci_free_dev() is called and desc->hdev is set to NULL before
btqcomipc_deinit() flushes the workqueue and disables IRQs.

If btss_worker() is still running or pending, it could attempt to access
desc->hdev (e.g., logging an error or updating stats), leading to a NULL
pointer dereference or use-after-free.

Should btqcomipc_deinit() be called before tearing down the HCI device?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260706-ipq5018-bluetooth-v4-0-350262a30959@outlook.com?part=4

^ permalink raw reply	[flat|nested] 26+ messages in thread

* RE: Add support for IPQ5018 Bluetooth
  2026-07-06 17:26   ` George Moussalem
  (?)
@ 2026-07-06 18:21   ` bluez.test.bot
  -1 siblings, 0 replies; 26+ messages in thread
From: bluez.test.bot @ 2026-07-06 18:21 UTC (permalink / raw)
  To: linux-bluetooth, george.moussalem

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

This is an automated email and please do not reply to this email.

Dear Submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository.

----- Output -----

error: patch failed: drivers/bluetooth/btqca.c:997
error: drivers/bluetooth/btqca.c: patch does not apply
hint: Use 'git am --show-current-patch' to see the failed patch

Please resolve the issue and submit the patches again.


---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 6/6] MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver
  2026-07-06 17:26   ` George Moussalem
  (?)
@ 2026-07-06 18:36   ` Luiz Augusto von Dentz
  2026-07-06 20:04     ` George Moussalem
  -1 siblings, 1 reply; 26+ messages in thread
From: Luiz Augusto von Dentz @ 2026-07-06 18:36 UTC (permalink / raw)
  To: george.moussalem
  Cc: Bartosz Golaszewski, Marcel Holtmann, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio,
	Philipp Zabel, linux-arm-msm, linux-bluetooth, devicetree,
	linux-kernel, Bartosz Golaszewski

Hi George,

On Mon, Jul 6, 2026 at 1:27 PM George Moussalem via B4 Relay
<devnull+george.moussalem.outlook.com@kernel.org> wrote:
>
> From: George Moussalem <george.moussalem@outlook.com>
>
> Add maintainers entry for Qualcomm IPQ5018 Bluetooth driver.
>
> Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
> ---
>  MAINTAINERS | 7 +++++++
>  1 file changed, 7 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0b9d7c8276ac..60f7251d1a16 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22289,6 +22289,13 @@ S:     Maintained
>  F:     Documentation/devicetree/bindings/regulator/vqmmc-ipq4019-regulator.yaml
>  F:     drivers/regulator/vqmmc-ipq4019-regulator.c
>
> +QUALCOMM IPQ5018 BLUETOOTH DRIVER
> +M:     George Moussalem <george.moussalem@outlook.com>

Do you work for Qualcomm or have access to their specs?

> +L:     linux-bluetooth@vger.kernel.org
> +S:     Maintained
> +F:     Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
> +F:     drivers/bluetooth/btqcomipc.c
> +
>  QUALCOMM IRIS VIDEO ACCELERATOR DRIVER
>  M:     Vikash Garodia <vikash.garodia@oss.qualcomm.com>
>  M:     Dikshita Agarwal <dikshita.agarwal@oss.qualcomm.com>
>
> --
> 2.53.0
>
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 6/6] MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver
  2026-07-06 18:36   ` Luiz Augusto von Dentz
@ 2026-07-06 20:04     ` George Moussalem
  2026-07-06 20:20       ` Luiz Augusto von Dentz
  0 siblings, 1 reply; 26+ messages in thread
From: George Moussalem @ 2026-07-06 20:04 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: Bartosz Golaszewski, Marcel Holtmann, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio,
	Philipp Zabel, linux-arm-msm, linux-bluetooth, devicetree,
	linux-kernel, Bartosz Golaszewski

Hi Luis,

On 7/6/26 22:36, Luiz Augusto von Dentz wrote:
> Hi George,
> 
> On Mon, Jul 6, 2026 at 1:27 PM George Moussalem via B4 Relay
> <devnull+george.moussalem.outlook.com@kernel.org> wrote:
>>
>> From: George Moussalem <george.moussalem@outlook.com>
>>
>> Add maintainers entry for Qualcomm IPQ5018 Bluetooth driver.
>>
>> Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
>> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
>> ---
>>  MAINTAINERS | 7 +++++++
>>  1 file changed, 7 insertions(+)
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 0b9d7c8276ac..60f7251d1a16 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -22289,6 +22289,13 @@ S:     Maintained
>>  F:     Documentation/devicetree/bindings/regulator/vqmmc-ipq4019-regulator.yaml
>>  F:     drivers/regulator/vqmmc-ipq4019-regulator.c
>>
>> +QUALCOMM IPQ5018 BLUETOOTH DRIVER
>> +M:     George Moussalem <george.moussalem@outlook.com>
> 
> Do you work for Qualcomm or have access to their specs?

No, I don't work for Qualcomm and don't have access to their specs either.

I'm purely contributing in the capacity of a volunteer.
> 
>> +L:     linux-bluetooth@vger.kernel.org
>> +S:     Maintained
>> +F:     Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
>> +F:     drivers/bluetooth/btqcomipc.c
>> +
>>  QUALCOMM IRIS VIDEO ACCELERATOR DRIVER
>>  M:     Vikash Garodia <vikash.garodia@oss.qualcomm.com>
>>  M:     Dikshita Agarwal <dikshita.agarwal@oss.qualcomm.com>
>>
>> --
>> 2.53.0
>>
>>
> 
> 
Best regards,
George


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 6/6] MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver
  2026-07-06 20:04     ` George Moussalem
@ 2026-07-06 20:20       ` Luiz Augusto von Dentz
  2026-07-07  8:04         ` Bartosz Golaszewski
  0 siblings, 1 reply; 26+ messages in thread
From: Luiz Augusto von Dentz @ 2026-07-06 20:20 UTC (permalink / raw)
  To: George Moussalem
  Cc: Bartosz Golaszewski, Marcel Holtmann, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio,
	Philipp Zabel, linux-arm-msm, linux-bluetooth, devicetree,
	linux-kernel, Bartosz Golaszewski

Hi George,

On Mon, Jul 6, 2026 at 4:04 PM George Moussalem
<george.moussalem@outlook.com> wrote:
>
> Hi Luis,
>
> On 7/6/26 22:36, Luiz Augusto von Dentz wrote:
> > Hi George,
> >
> > On Mon, Jul 6, 2026 at 1:27 PM George Moussalem via B4 Relay
> > <devnull+george.moussalem.outlook.com@kernel.org> wrote:
> >>
> >> From: George Moussalem <george.moussalem@outlook.com>
> >>
> >> Add maintainers entry for Qualcomm IPQ5018 Bluetooth driver.
> >>
> >> Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
> >> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
> >> ---
> >>  MAINTAINERS | 7 +++++++
> >>  1 file changed, 7 insertions(+)
> >>
> >> diff --git a/MAINTAINERS b/MAINTAINERS
> >> index 0b9d7c8276ac..60f7251d1a16 100644
> >> --- a/MAINTAINERS
> >> +++ b/MAINTAINERS
> >> @@ -22289,6 +22289,13 @@ S:     Maintained
> >>  F:     Documentation/devicetree/bindings/regulator/vqmmc-ipq4019-regulator.yaml
> >>  F:     drivers/regulator/vqmmc-ipq4019-regulator.c
> >>
> >> +QUALCOMM IPQ5018 BLUETOOTH DRIVER
> >> +M:     George Moussalem <george.moussalem@outlook.com>
> >
> > Do you work for Qualcomm or have access to their specs?
>
> No, I don't work for Qualcomm and don't have access to their specs either.
>
> I'm purely contributing in the capacity of a volunteer.

I appreciate the initiative, but we still need ACKs from Qualcomm for
code directly related to their hardware design. This is especially
true nowadays, as people seem confident enough to send code changes
done by an AI agent for hardware they don't have so they are never
tested.

> >
> >> +L:     linux-bluetooth@vger.kernel.org
> >> +S:     Maintained
> >> +F:     Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
> >> +F:     drivers/bluetooth/btqcomipc.c
> >> +
> >>  QUALCOMM IRIS VIDEO ACCELERATOR DRIVER
> >>  M:     Vikash Garodia <vikash.garodia@oss.qualcomm.com>
> >>  M:     Dikshita Agarwal <dikshita.agarwal@oss.qualcomm.com>
> >>
> >> --
> >> 2.53.0
> >>
> >>
> >
> >
> Best regards,
> George
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 5/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
  2026-07-06 17:26   ` George Moussalem
  (?)
@ 2026-07-07  7:51   ` Konrad Dybcio
  2026-07-07  7:51     ` Konrad Dybcio
  -1 siblings, 1 reply; 26+ messages in thread
From: Konrad Dybcio @ 2026-07-07  7:51 UTC (permalink / raw)
  To: george.moussalem, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Bjorn Andersson, Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	Bartosz Golaszewski

On 7/6/26 7:26 PM, George Moussalem via B4 Relay wrote:
> From: George Moussalem <george.moussalem@outlook.com>
> 
> Add nodes for the reserved memory carveout and Bluetooth.
> 
> Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
> ---

[...]

> @@ -136,6 +153,11 @@ reserved-memory {
>  		#size-cells = <2>;
>  		ranges;
>  
> +		btss_region: bluetooth@7000000 {
> +			reg = <0x0 0x07000000 0x0 0x58000>;

FWIW RAM starts at 0x4000_0000 - I'm thinking whether this should
just be the 'reg' value of the bluetooth node. This makes even more
sense as you write to this region using I/O accessors

Konrad

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 5/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
  2026-07-07  7:51   ` Konrad Dybcio
@ 2026-07-07  7:51     ` Konrad Dybcio
  2026-07-07  8:12       ` George Moussalem
  0 siblings, 1 reply; 26+ messages in thread
From: Konrad Dybcio @ 2026-07-07  7:51 UTC (permalink / raw)
  To: george.moussalem, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Bjorn Andersson, Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	Bartosz Golaszewski

On 7/7/26 9:51 AM, Konrad Dybcio wrote:
> On 7/6/26 7:26 PM, George Moussalem via B4 Relay wrote:
>> From: George Moussalem <george.moussalem@outlook.com>
>>
>> Add nodes for the reserved memory carveout and Bluetooth.
>>
>> Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
>> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
>> ---
> 
> [...]
> 
>> @@ -136,6 +153,11 @@ reserved-memory {
>>  		#size-cells = <2>;
>>  		ranges;
>>  
>> +		btss_region: bluetooth@7000000 {
>> +			reg = <0x0 0x07000000 0x0 0x58000>;
> 
> FWIW RAM starts at 0x4000_0000 - I'm thinking whether this should
> just be the 'reg' value of the bluetooth node. This makes even more
> sense as you write to this region using I/O accessors

FWIW2: The region is called "BT_RAM"

Konrad

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 6/6] MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver
  2026-07-06 20:20       ` Luiz Augusto von Dentz
@ 2026-07-07  8:04         ` Bartosz Golaszewski
  2026-07-07  8:15           ` George Moussalem
  0 siblings, 1 reply; 26+ messages in thread
From: Bartosz Golaszewski @ 2026-07-07  8:04 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: Bartosz Golaszewski, Marcel Holtmann, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio,
	Philipp Zabel, linux-arm-msm, linux-bluetooth, devicetree,
	linux-kernel, Bartosz Golaszewski, George Moussalem

On Mon, 6 Jul 2026 22:20:45 +0200, Luiz Augusto von Dentz
<luiz.dentz@gmail.com> said:
> Hi George,
>
> On Mon, Jul 6, 2026 at 4:04 PM George Moussalem
> <george.moussalem@outlook.com> wrote:
>>
>> Hi Luis,
>>
>> On 7/6/26 22:36, Luiz Augusto von Dentz wrote:
>> > Hi George,
>> >
>> > On Mon, Jul 6, 2026 at 1:27 PM George Moussalem via B4 Relay
>> > <devnull+george.moussalem.outlook.com@kernel.org> wrote:
>> >>
>> >> From: George Moussalem <george.moussalem@outlook.com>
>> >>
>> >> Add maintainers entry for Qualcomm IPQ5018 Bluetooth driver.
>> >>
>> >> Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
>> >> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
>> >> ---
>> >>  MAINTAINERS | 7 +++++++
>> >>  1 file changed, 7 insertions(+)
>> >>
>> >> diff --git a/MAINTAINERS b/MAINTAINERS
>> >> index 0b9d7c8276ac..60f7251d1a16 100644
>> >> --- a/MAINTAINERS
>> >> +++ b/MAINTAINERS
>> >> @@ -22289,6 +22289,13 @@ S:     Maintained
>> >>  F:     Documentation/devicetree/bindings/regulator/vqmmc-ipq4019-regulator.yaml
>> >>  F:     drivers/regulator/vqmmc-ipq4019-regulator.c
>> >>
>> >> +QUALCOMM IPQ5018 BLUETOOTH DRIVER
>> >> +M:     George Moussalem <george.moussalem@outlook.com>
>> >
>> > Do you work for Qualcomm or have access to their specs?
>>
>> No, I don't work for Qualcomm and don't have access to their specs either.
>>
>> I'm purely contributing in the capacity of a volunteer.
>
> I appreciate the initiative, but we still need ACKs from Qualcomm for
> code directly related to their hardware design. This is especially
> true nowadays, as people seem confident enough to send code changes
> done by an AI agent for hardware they don't have so they are never
> tested.
>

Georde: feel free to fold this driver under the existing entry for Qualcomm
bluetooth drivers. You can add yourself as a reviewer if you want to be Cc'ed
on future patches.

Bartosz

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 5/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
  2026-07-07  7:51     ` Konrad Dybcio
@ 2026-07-07  8:12       ` George Moussalem
  2026-07-07  8:27         ` Konrad Dybcio
  0 siblings, 1 reply; 26+ messages in thread
From: George Moussalem @ 2026-07-07  8:12 UTC (permalink / raw)
  To: Konrad Dybcio, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Bjorn Andersson, Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	Bartosz Golaszewski

On 7/7/26 11:51, Konrad Dybcio wrote:
> On 7/7/26 9:51 AM, Konrad Dybcio wrote:
>> On 7/6/26 7:26 PM, George Moussalem via B4 Relay wrote:
>>> From: George Moussalem <george.moussalem@outlook.com>
>>>
>>> Add nodes for the reserved memory carveout and Bluetooth.
>>>
>>> Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
>>> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
>>> ---
>>
>> [...]
>>
>>> @@ -136,6 +153,11 @@ reserved-memory {
>>>  		#size-cells = <2>;
>>>  		ranges;
>>>  
>>> +		btss_region: bluetooth@7000000 {
>>> +			reg = <0x0 0x07000000 0x0 0x58000>;
>>
>> FWIW RAM starts at 0x4000_0000 - I'm thinking whether this should
>> just be the 'reg' value of the bluetooth node. This makes even more
>> sense as you write to this region using I/O accessors

I wasn't sure whether setting the unit address to the memory region it
uses and write to. Perhaps Bjorn/Krzysztof can provide guidance?
So essentially, the proposal would be for the bluetooth node without a
reg property to look like:

bluetooth@7000000 {
...
	memory-region = <&btss_region>	--> which starts at 0x07000000
};
 > FWIW2: The region is called "BT_RAM"
> 
> Konrad

Thanks, I'll update the node name of the memory region to bt_ram@7000000

Best regards,
George

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 6/6] MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver
  2026-07-07  8:04         ` Bartosz Golaszewski
@ 2026-07-07  8:15           ` George Moussalem
  0 siblings, 0 replies; 26+ messages in thread
From: George Moussalem @ 2026-07-07  8:15 UTC (permalink / raw)
  To: Bartosz Golaszewski, Luiz Augusto von Dentz
  Cc: Marcel Holtmann, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Philipp Zabel, linux-arm-msm,
	linux-bluetooth, devicetree, linux-kernel, Bartosz Golaszewski

On 7/7/26 12:04, Bartosz Golaszewski wrote:
> On Mon, 6 Jul 2026 22:20:45 +0200, Luiz Augusto von Dentz
> <luiz.dentz@gmail.com> said:
>> Hi George,
>>
>> On Mon, Jul 6, 2026 at 4:04 PM George Moussalem
>> <george.moussalem@outlook.com> wrote:
>>>
>>> Hi Luis,
>>>
>>> On 7/6/26 22:36, Luiz Augusto von Dentz wrote:
>>>> Hi George,
>>>>
>>>> On Mon, Jul 6, 2026 at 1:27 PM George Moussalem via B4 Relay
>>>> <devnull+george.moussalem.outlook.com@kernel.org> wrote:
>>>>>
>>>>> From: George Moussalem <george.moussalem@outlook.com>
>>>>>
>>>>> Add maintainers entry for Qualcomm IPQ5018 Bluetooth driver.
>>>>>
>>>>> Acked-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
>>>>> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
>>>>> ---
>>>>>  MAINTAINERS | 7 +++++++
>>>>>  1 file changed, 7 insertions(+)
>>>>>
>>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>>> index 0b9d7c8276ac..60f7251d1a16 100644
>>>>> --- a/MAINTAINERS
>>>>> +++ b/MAINTAINERS
>>>>> @@ -22289,6 +22289,13 @@ S:     Maintained
>>>>>  F:     Documentation/devicetree/bindings/regulator/vqmmc-ipq4019-regulator.yaml
>>>>>  F:     drivers/regulator/vqmmc-ipq4019-regulator.c
>>>>>
>>>>> +QUALCOMM IPQ5018 BLUETOOTH DRIVER
>>>>> +M:     George Moussalem <george.moussalem@outlook.com>
>>>>
>>>> Do you work for Qualcomm or have access to their specs?
>>>
>>> No, I don't work for Qualcomm and don't have access to their specs either.
>>>
>>> I'm purely contributing in the capacity of a volunteer.
>>
>> I appreciate the initiative, but we still need ACKs from Qualcomm for
>> code directly related to their hardware design. This is especially
>> true nowadays, as people seem confident enough to send code changes
>> done by an AI agent for hardware they don't have so they are never
>> tested.
>>
> 
> Georde: feel free to fold this driver under the existing entry for Qualcomm
> bluetooth drivers. You can add yourself as a reviewer if you want to be Cc'ed
> on future patches.

Thanks, will add a file entry into the existing entry. The bindings are
already covered by:
F:	Documentation/devicetree/bindings/net/bluetooth/qcom,*

> 
> Bartosz

Cheers,
George

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH v4 5/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
  2026-07-07  8:12       ` George Moussalem
@ 2026-07-07  8:27         ` Konrad Dybcio
  0 siblings, 0 replies; 26+ messages in thread
From: Konrad Dybcio @ 2026-07-07  8:27 UTC (permalink / raw)
  To: George Moussalem, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Bjorn Andersson, Konrad Dybcio, Philipp Zabel
  Cc: linux-arm-msm, linux-bluetooth, devicetree, linux-kernel,
	Bartosz Golaszewski

On 7/7/26 10:12 AM, George Moussalem wrote:
> On 7/7/26 11:51, Konrad Dybcio wrote:
>> On 7/7/26 9:51 AM, Konrad Dybcio wrote:
>>> On 7/6/26 7:26 PM, George Moussalem via B4 Relay wrote:
>>>> From: George Moussalem <george.moussalem@outlook.com>
>>>>
>>>> Add nodes for the reserved memory carveout and Bluetooth.
>>>>
>>>> Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
>>>> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
>>>> ---
>>>
>>> [...]
>>>
>>>> @@ -136,6 +153,11 @@ reserved-memory {
>>>>  		#size-cells = <2>;
>>>>  		ranges;
>>>>  
>>>> +		btss_region: bluetooth@7000000 {
>>>> +			reg = <0x0 0x07000000 0x0 0x58000>;
>>>
>>> FWIW RAM starts at 0x4000_0000 - I'm thinking whether this should
>>> just be the 'reg' value of the bluetooth node. This makes even more
>>> sense as you write to this region using I/O accessors
> 
> I wasn't sure whether setting the unit address to the memory region it
> uses and write to. Perhaps Bjorn/Krzysztof can provide guidance?
> So essentially, the proposal would be for the bluetooth node without a
> reg property to look like:
> 
> bluetooth@7000000 {
> ...
> 	memory-region = <&btss_region>	--> which starts at 0x07000000

I was proposing that we remove the reserved-memory region and define
this register range only through the 'reg' property.

> };
>  > FWIW2: The region is called "BT_RAM"
>>
>> Konrad
> 
> Thanks, I'll update the node name of the memory region to bt_ram@7000000

Node names (between ':' and '@') must not contain underscores, use a
hyphen there.

Konrad

^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2026-07-07  8:27 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-06 17:26 [PATCH v4 0/6] Add support for IPQ5018 Bluetooth George Moussalem via B4 Relay
2026-07-06 17:26 ` George Moussalem
2026-07-06 17:26 ` [PATCH v4 1/6] dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller George Moussalem via B4 Relay
2026-07-06 17:26   ` George Moussalem
2026-07-06 18:21   ` Add support for IPQ5018 Bluetooth bluez.test.bot
2026-07-06 17:26 ` [PATCH v4 2/6] Bluetooth: btqca: Add IPQ5018 support George Moussalem via B4 Relay
2026-07-06 17:26   ` George Moussalem
2026-07-06 17:26 ` [PATCH v4 3/6] firmware: qcom: scm: Add support for setting Bluetooth power modes George Moussalem via B4 Relay
2026-07-06 17:26   ` George Moussalem
2026-07-06 17:36   ` sashiko-bot
2026-07-06 17:26 ` [PATCH v4 4/6] Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver George Moussalem via B4 Relay
2026-07-06 17:26   ` George Moussalem
2026-07-06 17:42   ` sashiko-bot
2026-07-06 17:26 ` [PATCH v4 5/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support George Moussalem via B4 Relay
2026-07-06 17:26   ` George Moussalem
2026-07-07  7:51   ` Konrad Dybcio
2026-07-07  7:51     ` Konrad Dybcio
2026-07-07  8:12       ` George Moussalem
2026-07-07  8:27         ` Konrad Dybcio
2026-07-06 17:26 ` [PATCH v4 6/6] MAINTAINERS: Add entry for Qualcomm IPQ5018 Bluetooth driver George Moussalem via B4 Relay
2026-07-06 17:26   ` George Moussalem
2026-07-06 18:36   ` Luiz Augusto von Dentz
2026-07-06 20:04     ` George Moussalem
2026-07-06 20:20       ` Luiz Augusto von Dentz
2026-07-07  8:04         ` Bartosz Golaszewski
2026-07-07  8:15           ` George Moussalem

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.