* [PATCH V3 5/8] arm64: dts: imx95-19x19-evk: Describe the PCIe M.2 Key E connector
From: Sherry Sun (OSS) @ 2026-06-26 2:31 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, Frank.Li, s.hauer, kernel, festevam,
amitkumar.karwar, neeraj.sanjaykale, marcel, luiz.dentz,
hongxing.zhu, l.stach, lpieralisi, kwilczynski, mani, bhelgaas,
brgl
Cc: imx, linux-pci, linux-arm-kernel, devicetree, linux-kernel,
linux-bluetooth, linux-pm, sherry.sun
In-Reply-To: <20260626023126.2189931-1-sherry.sun@oss.nxp.com>
From: Sherry Sun <sherry.sun@nxp.com>
The i.MX95-19x19-EVK has the PCIe M.2 Mechanical Key E connector to
connect wireless connectivity cards over PCIe and UART interfaces. Hence,
describe the connector node and link it with the PCIe 0 Root Port and
LPUART5 nodes through graph port/endpoint.
Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
---
.../boot/dts/freescale/imx95-19x19-evk.dts | 55 ++++++++++++++-----
1 file changed, 41 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
index c08731dfb1ee..d2c0345f0d61 100644
--- a/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx95-19x19-evk.dts
@@ -57,6 +57,37 @@ memory@80000000 {
reg = <0x0 0x80000000 0 0x80000000>;
};
+ m2-connector {
+ compatible = "pcie-m2-e-connector";
+ vpcie3v3-supply = <®_m2_pwr>;
+ w-disable1-gpios = <&i2c7_pcal6524 6 GPIO_ACTIVE_LOW>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0>;
+ m2_e_pcie_ep: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&pcie0_port0_ep>;
+ };
+ };
+
+ port@3 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <3>;
+ m2_e_uart_ep: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&lpuart5_ep>;
+ };
+ };
+ };
+ };
+
fan0: pwm-fan {
compatible = "pwm-fan";
#cooling-cells = <2>;
@@ -145,16 +176,6 @@ reg_m2_pwr: regulator-m2-pwr {
startup-delay-us = <5000>;
};
- reg_pcie0: regulator-pcie {
- compatible = "regulator-fixed";
- regulator-name = "PCIE_WLAN_EN";
- regulator-min-microvolt = <3300000>;
- regulator-max-microvolt = <3300000>;
- vin-supply = <®_m2_pwr>;
- gpio = <&i2c7_pcal6524 6 GPIO_ACTIVE_HIGH>;
- enable-active-high;
- };
-
reg_slot_pwr: regulator-slot-pwr {
compatible = "regulator-fixed";
regulator-name = "PCIe slot-power";
@@ -477,8 +498,10 @@ &lpuart5 {
pinctrl-0 = <&pinctrl_uart5>;
status = "okay";
- bluetooth {
- compatible = "nxp,88w8987-bt";
+ port {
+ lpuart5_ep: endpoint {
+ remote-endpoint = <&m2_e_uart_ep>;
+ };
};
};
@@ -555,8 +578,12 @@ &pcie0_ep {
&pcie0_port0 {
reset-gpios = <&i2c7_pcal6524 5 GPIO_ACTIVE_LOW>;
- vpcie-supply = <®_pcie0>;
- vpcie3v3aux-supply = <®_pcie0>;
+
+ port {
+ pcie0_port0_ep: endpoint {
+ remote-endpoint = <&m2_e_pcie_ep>;
+ };
+ };
};
&pcie1 {
--
2.50.1
^ permalink raw reply related
* [PATCH V3 4/8] arm64: dts: imx8mq-evk: Describe the PCIe M.2 Key E connector
From: Sherry Sun (OSS) @ 2026-06-26 2:31 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, Frank.Li, s.hauer, kernel, festevam,
amitkumar.karwar, neeraj.sanjaykale, marcel, luiz.dentz,
hongxing.zhu, l.stach, lpieralisi, kwilczynski, mani, bhelgaas,
brgl
Cc: imx, linux-pci, linux-arm-kernel, devicetree, linux-kernel,
linux-bluetooth, linux-pm, sherry.sun
In-Reply-To: <20260626023126.2189931-1-sherry.sun@oss.nxp.com>
From: Sherry Sun <sherry.sun@nxp.com>
The i.MX8MQ-EVK has the PCIe M.2 Mechanical Key E connector to connect
wireless connectivity cards over PCIe and UART interfaces. Hence,
describe the connector node and link it with the PCIe 1 Root Port and
UART3 nodes through graph port/endpoint.
Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
---
arch/arm64/boot/dts/freescale/imx8mq-evk.dts | 44 ++++++++++++++++++--
1 file changed, 40 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/boot/dts/freescale/imx8mq-evk.dts b/arch/arm64/boot/dts/freescale/imx8mq-evk.dts
index 71504a0af87f..482e5203e879 100644
--- a/arch/arm64/boot/dts/freescale/imx8mq-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx8mq-evk.dts
@@ -21,6 +21,36 @@ memory@40000000 {
reg = <0x00000000 0x40000000 0 0xc0000000>;
};
+ m2-connector {
+ compatible = "pcie-m2-e-connector";
+ vpcie3v3-supply = <®_pcie1>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0>;
+ m2_e_pcie_ep: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&pcie1_port0_ep>;
+ };
+ };
+
+ port@3 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <3>;
+ m2_e_uart_ep: endpoint@0 {
+ reg = <0>;
+ remote-endpoint = <&uart3_ep>;
+ };
+ };
+ };
+ };
+
pcie0_refclk: pcie0-refclk {
compatible = "fixed-clock";
#clock-cells = <0>;
@@ -420,8 +450,12 @@ &pcie1_ep {
&pcie1_port0 {
reset-gpios = <&gpio5 12 GPIO_ACTIVE_LOW>;
- vpcie-supply = <®_pcie1>;
- vpcie3v3aux-supply = <®_pcie1>;
+
+ port {
+ pcie1_port0_ep: endpoint {
+ remote-endpoint = <&m2_e_pcie_ep>;
+ };
+ };
};
&pgc_gpu {
@@ -506,8 +540,10 @@ &uart3 { /* BT */
uart-has-rtscts;
status = "okay";
- bluetooth {
- compatible = "nxp,88w8987-bt";
+ port {
+ uart3_ep: endpoint {
+ remote-endpoint = <&m2_e_uart_ep>;
+ };
};
};
--
2.50.1
^ permalink raw reply related
* [PATCH V3 3/8] Bluetooth: btnxpuart: Add M.2 Bluetooth device support using pwrseq
From: Sherry Sun (OSS) @ 2026-06-26 2:31 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, Frank.Li, s.hauer, kernel, festevam,
amitkumar.karwar, neeraj.sanjaykale, marcel, luiz.dentz,
hongxing.zhu, l.stach, lpieralisi, kwilczynski, mani, bhelgaas,
brgl
Cc: imx, linux-pci, linux-arm-kernel, devicetree, linux-kernel,
linux-bluetooth, linux-pm, sherry.sun
In-Reply-To: <20260626023126.2189931-1-sherry.sun@oss.nxp.com>
From: Sherry Sun <sherry.sun@nxp.com>
Power supply to the M.2 Bluetooth device attached to the host using M.2
connector is controlled using the 'uart' pwrseq device. So add support for
getting the pwrseq device if the OF graph link is present. Once obtained,
pwrseq_power_on() is called to power up the M.2 Bluetooth card. The power
sequencer descriptor is obtained via devm_pwrseq_get(), so the power-off
and cleanup are handled automatically when the device is unbound.
Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
drivers/bluetooth/btnxpuart.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c
index e7036a48ce48..438ccaa2b56d 100644
--- a/drivers/bluetooth/btnxpuart.c
+++ b/drivers/bluetooth/btnxpuart.c
@@ -9,6 +9,8 @@
#include <linux/serdev.h>
#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/pwrseq/consumer.h>
#include <linux/skbuff.h>
#include <linux/unaligned.h>
#include <linux/firmware.h>
@@ -1866,6 +1868,18 @@ static int nxp_serdev_probe(struct serdev_device *serdev)
return err;
}
+ if (of_graph_is_present(dev_of_node(&serdev->ctrl->dev))) {
+ struct pwrseq_desc *pwrseq;
+
+ pwrseq = devm_pwrseq_get(&serdev->ctrl->dev, "uart");
+ if (IS_ERR(pwrseq))
+ return PTR_ERR(pwrseq);
+
+ err = pwrseq_power_on(pwrseq);
+ if (err)
+ return err;
+ }
+
/* Initialize and register HCI device */
hdev = hci_alloc_dev();
if (!hdev) {
--
2.50.1
^ permalink raw reply related
* [PATCH V3 2/8] power: sequencing: pcie-m2: Add PCI ID for NXP 88W9098 and AW693 Bluetooth
From: Sherry Sun (OSS) @ 2026-06-26 2:31 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, Frank.Li, s.hauer, kernel, festevam,
amitkumar.karwar, neeraj.sanjaykale, marcel, luiz.dentz,
hongxing.zhu, l.stach, lpieralisi, kwilczynski, mani, bhelgaas,
brgl
Cc: imx, linux-pci, linux-arm-kernel, devicetree, linux-kernel,
linux-bluetooth, linux-pm, sherry.sun
In-Reply-To: <20260626023126.2189931-1-sherry.sun@oss.nxp.com>
From: Sherry Sun <sherry.sun@nxp.com>
88W9098 is a NXP Wi-Fi/BT combo chip with PCI device ID 0x2b43 under
Marvell Extended vendor ID. AW693 is a NXP Wi-Fi/BT combo chip with
PCI device ID 0x3003 under NXP/Philips vendor ID.
Add both chips to pwrseq_m2_pci_ids[] so that the pwrseq-pcie-m2 driver
can create the Bluetooth serdev device when these cards are inserted into
a PCIe M.2 Key E connector.
Both chips use "nxp,88w8987-bt" as the serdev compatible string, which
is the entry point for the btnxpuart driver. The driver identifies the
actual chip variant at runtime via chip ID auto-detection and loads the
appropriate firmware accordingly.
Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
---
drivers/power/sequencing/pwrseq-pcie-m2.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c
index b5ed80d03953..e3ba9169144d 100644
--- a/drivers/power/sequencing/pwrseq-pcie-m2.c
+++ b/drivers/power/sequencing/pwrseq-pcie-m2.c
@@ -186,6 +186,10 @@ static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq,
}
static const struct pci_device_id pwrseq_m2_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_MARVELL_EXT, 0x2b43),
+ .driver_data = (kernel_ulong_t)"nxp,88w8987-bt" },
+ { PCI_DEVICE(PCI_VENDOR_ID_PHILIPS, 0x3003),
+ .driver_data = (kernel_ulong_t)"nxp,88w8987-bt" },
{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1103),
.driver_data = (kernel_ulong_t)"qcom,wcn6855-bt" },
{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1107),
--
2.50.1
^ permalink raw reply related
* [PATCH V3 1/8] PCI: imx6: Add skip_pwrctrl_off flag support
From: Sherry Sun (OSS) @ 2026-06-26 2:31 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, Frank.Li, s.hauer, kernel, festevam,
amitkumar.karwar, neeraj.sanjaykale, marcel, luiz.dentz,
hongxing.zhu, l.stach, lpieralisi, kwilczynski, mani, bhelgaas,
brgl
Cc: imx, linux-pci, linux-arm-kernel, devicetree, linux-kernel,
linux-bluetooth, linux-pm, sherry.sun
In-Reply-To: <20260626023126.2189931-1-sherry.sun@oss.nxp.com>
From: Sherry Sun <sherry.sun@nxp.com>
Use dw_pcie_rp::skip_pwrctrl_off to avoid powering off devices during
suspend to preserve wakeup capability of the devices and also not to power
on the devices in the init path.
This allows controller power-off to be skipped when some devices(e.g. M.2
cards key E without auxiliary power) required to support PCIe L2 link state
and wake-up mechanisms.
Move pci_pwrctrl_create_devices() to imx_pcie_probe() so that it is only
called once during probe, similar to other regulator_get calls.
Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
---
drivers/pci/controller/dwc/pci-imx6.c | 43 ++++++++++++++++-----------
1 file changed, 25 insertions(+), 18 deletions(-)
diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c
index 0fa716d1ed75..0685573fee71 100644
--- a/drivers/pci/controller/dwc/pci-imx6.c
+++ b/drivers/pci/controller/dwc/pci-imx6.c
@@ -1382,16 +1382,12 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
}
}
- ret = pci_pwrctrl_create_devices(dev);
- if (ret) {
- dev_err(dev, "failed to create pwrctrl devices\n");
- goto err_reg_disable;
- }
-
- ret = pci_pwrctrl_power_on_devices(dev);
- if (ret) {
- dev_err(dev, "failed to power on pwrctrl devices\n");
- goto err_pwrctrl_destroy;
+ if (!pp->skip_pwrctrl_off) {
+ ret = pci_pwrctrl_power_on_devices(dev);
+ if (ret) {
+ dev_err(dev, "failed to power on pwrctrl devices\n");
+ goto err_reg_disable;
+ }
}
ret = imx_pcie_clk_enable(imx_pcie);
@@ -1460,10 +1456,8 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
err_clk_disable:
imx_pcie_clk_disable(imx_pcie);
err_pwrctrl_power_off:
- pci_pwrctrl_power_off_devices(dev);
-err_pwrctrl_destroy:
- if (ret != -EPROBE_DEFER)
- pci_pwrctrl_destroy_devices(dev);
+ if (!pp->skip_pwrctrl_off)
+ pci_pwrctrl_power_off_devices(dev);
err_reg_disable:
if (imx_pcie->vpcie)
regulator_disable(imx_pcie->vpcie);
@@ -1482,7 +1476,8 @@ static void imx_pcie_host_exit(struct dw_pcie_rp *pp)
}
imx_pcie_clk_disable(imx_pcie);
- pci_pwrctrl_power_off_devices(pci->dev);
+ if (!pci->pp.skip_pwrctrl_off)
+ pci_pwrctrl_power_off_devices(pci->dev);
if (imx_pcie->vpcie)
regulator_disable(imx_pcie->vpcie);
}
@@ -1954,11 +1949,15 @@ static int imx_pcie_probe(struct platform_device *pdev)
if (ret)
return ret;
+ ret = pci_pwrctrl_create_devices(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to create pwrctrl devices\n");
+
pci->use_parent_dt_ranges = true;
if (imx_pcie->drvdata->mode == DW_PCIE_EP_TYPE) {
ret = imx_add_pcie_ep(imx_pcie, pdev);
if (ret < 0)
- return ret;
+ goto err_pwrctrl_destroy;
/*
* FIXME: Only single Device (EPF) is supported due to the
@@ -1973,7 +1972,7 @@ static int imx_pcie_probe(struct platform_device *pdev)
pci->pp.use_atu_msg = true;
ret = dw_pcie_host_init(&pci->pp);
if (ret < 0)
- return ret;
+ goto err_pwrctrl_destroy;
if (pci_msi_enabled()) {
u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_MSI);
@@ -1985,16 +1984,24 @@ static int imx_pcie_probe(struct platform_device *pdev)
}
return 0;
+
+err_pwrctrl_destroy:
+ if (ret != -EPROBE_DEFER)
+ pci_pwrctrl_destroy_devices(dev);
+ return ret;
}
static void imx_pcie_shutdown(struct platform_device *pdev)
{
struct imx_pcie *imx_pcie = platform_get_drvdata(pdev);
+ struct dw_pcie *pci = imx_pcie->pci;
+ struct dw_pcie_rp *pp = &pci->pp;
/* bring down link, so bootloader gets clean state in case of reboot */
imx_pcie_assert_core_reset(imx_pcie);
imx_pcie_assert_perst(imx_pcie, true);
- pci_pwrctrl_power_off_devices(&pdev->dev);
+ if (!pp->skip_pwrctrl_off)
+ pci_pwrctrl_power_off_devices(&pdev->dev);
pci_pwrctrl_destroy_devices(&pdev->dev);
}
--
2.50.1
^ permalink raw reply related
* [PATCH V3 0/8] Add PCIe M.2 Key E connector support for NXP i.MX boards
From: Sherry Sun (OSS) @ 2026-06-26 2:31 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, Frank.Li, s.hauer, kernel, festevam,
amitkumar.karwar, neeraj.sanjaykale, marcel, luiz.dentz,
hongxing.zhu, l.stach, lpieralisi, kwilczynski, mani, bhelgaas,
brgl
Cc: imx, linux-pci, linux-arm-kernel, devicetree, linux-kernel,
linux-bluetooth, linux-pm, sherry.sun
From: Sherry Sun <sherry.sun@nxp.com>
This series adds support for NXP Wi-Fi/BT combo chips (88W9098, AW693)
inserted into PCIe M.2 Key E connectors on several i.MX EVK/MEK boards.
For M.2 cards that rely on PCIe L2 link state and wake-up mechanisms, the
card must remain powered during suspend. Patch #1 uses the existing
dw_pcie_rp::skip_pwrctrl_off flag to skip power-off during suspend and skip
power-on during the init path.
Also the btnxpuart driver is extended to obtain a pwrseq descriptor via the
OF graph on the UART controller device in patch #3.
Note: Patch #4-8 in this patch set depends on the following [1] and [2]
DTS patches.
[1] https://lore.kernel.org/all/20260616105201.3214395-1-sherry.sun@oss.nxp.com/
[2] https://lore.kernel.org/all/20260520084904.2424253-1-sherry.sun@oss.nxp.com/
---
Changes in V3:
1. Move pci_pwrctrl_create_devices() to imx_pcie_probe() in parch #1,
similar to other regulator_get calls as suggested by Frank.
2. Collected the tags.
Changes in V2:
1. Rebased on top of 7.1.0.
2. Removed the power_off error handling in patch #3 because pwrseq_put()
would call pwrseq_power_off() to automatically clean.
3. Collected the tags.
---
Sherry Sun (8):
PCI: imx6: Add skip_pwrctrl_off flag support
power: sequencing: pcie-m2: Add PCI ID for NXP 88W9098 and AW693
Bluetooth
Bluetooth: btnxpuart: Add M.2 Bluetooth device support using pwrseq
arm64: dts: imx8mq-evk: Describe the PCIe M.2 Key E connector
arm64: dts: imx95-19x19-evk: Describe the PCIe M.2 Key E connector
arm64: dts: imx8dxl-evk: Describe the PCIe M.2 Key E connector
arm64: dts: imx8qm-mek: Describe the PCIe M.2 Key E connector
arm64: dts: imx8qxp-mek: Describe the PCIe M.2 Key E connector
arch/arm64/boot/dts/freescale/imx8dxl-evk.dts | 56 +++++++++++++-----
arch/arm64/boot/dts/freescale/imx8mq-evk.dts | 44 ++++++++++++--
arch/arm64/boot/dts/freescale/imx8qm-mek.dts | 58 ++++++++++++++-----
arch/arm64/boot/dts/freescale/imx8qxp-mek.dts | 54 ++++++++++++-----
.../boot/dts/freescale/imx95-19x19-evk.dts | 55 +++++++++++++-----
drivers/bluetooth/btnxpuart.c | 14 +++++
drivers/pci/controller/dwc/pci-imx6.c | 43 ++++++++------
drivers/power/sequencing/pwrseq-pcie-m2.c | 4 ++
8 files changed, 250 insertions(+), 78 deletions(-)
--
2.50.1
^ permalink raw reply
* RE: RTL8852BE (0bda:b853) Bluetooth initialization fails with -16 on ThinkBook 14+ 2026
From: Zenm Chen @ 2026-06-26 0:38 UTC (permalink / raw)
To: max.chou, hildawu; +Cc: michail383krasnov, linux-bluetooth
In-Reply-To: <b2bc01ba-edc8-4e73-9358-a3bf1d7c569d@mail.ru>
Hi Max and Hilda,
Could you please take a look at this issue? thanks!
https://lore.kernel.org/linux-bluetooth/b2bc01ba-edc8-4e73-9358-a3bf1d7c569d@mail.ru/
^ permalink raw reply
* Re: [PATCH v5] Bluetooth: btrtl: Add firmware format v3 support
From: Luiz Augusto von Dentz @ 2026-06-25 18:39 UTC (permalink / raw)
To: Hilda Wu
Cc: marcel, linux-bluetooth, linux-kernel, alex_lu, jason_mao,
zoey_zhou, max.chou, kidman
In-Reply-To: <20260624053334.3151885-1-hildawu@realtek.com>
Hi Hilda,
On Wed, Jun 24, 2026 at 1:33 AM Hilda Wu <hildawu@realtek.com> wrote:
>
> Realtek updated its Bluetooth firmware format to v3.
> This patch extends the btrtl driver to recognise and parse the new v3 file
> format, including:
> - New signature string and image ID definitions
> - Extension of btrtl_device_info to store v3-specific metadata
> - Logic to extract and load firmware data out of v3 images
> - Maintains compatibility with existing v2 firmware format
>
> This is required for future Realtek Bluetooth chips that ship with
> v3 firmware.
> The RTL8922D is the first IC to use firmware format V3, so the following
> example uses the RTL8922D's log as expected fw format v3 output:
>
> Bluetooth: btrtl_read_chip_id() hci0: RTL: chip_id status=0x00 id=0x37
> Bluetooth: btrtl_initialize() hci0: RTL: examining hci_ver=0d
> hci_rev=000d lmp_ver=0d lmp_subver=8922
> Bluetooth: rtl_read_rom_version() hci0: RTL: rom_version status=0 version=1
> Bluetooth: btrtl_initialize() hci0: RTL: btrtl_initialize: key id 0
> Bluetooth: rtl_load_file() hci0: RTL: loading rtl_bt/rtl8922du_fw.bin
> Bluetooth: rtl_load_file() hci0: RTL: loading rtl_bt/rtl8922du_config.bin
> Bluetooth: rtlbt_parse_firmware_v3() hci0: RTL: key id 0
> Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image (f000:00), chip id
> 55, cut 0x02, len 00007185
> Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image version: 35fd7908
> Bluetooth: rtlbt_parse_config() hci0: RTL: config file:
> rtl_bt/rtl8922du_config_f000.bin
> Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image (f002:00), chip id
> 55, cut 0x02, len 000078f5
> Bluetooth: rtlbt_parse_section_v3() hci0: RTL: image version: 47b6874d
> Bluetooth: rtlbt_parse_config() hci0: RTL: config file:
> rtl_bt/rtl8922du_config_f002.bin
> Bluetooth: rtlbt_parse_firmware_v3() hci0: RTL: image payload total len:
> 0x0000ea7a
> Bluetooth: rtl_finalize_download() hci0: RTL: Watchdog reset status 00
> Bluetooth: rtl_finalize_download() hci0: RTL: fw version 0x47b6874d
>
> Signed-off-by: Alex Lu <alex_lu@realsil.com.cn>
> Signed-off-by: Zoey Zhou <zoey_zhou@realsil.com.cn>
> Signed-off-by: Hilda Wu <hildawu@realtek.com>
>
> ---
> V4 -> V5::
> - Add independent support for RTL8922D section
> - Introduce macros to improve code readability
> - Document firmware format v3 and its differences
> - Align implementation with reviewer feedback
>
> V3 -> V4:
> - Rework skb->data access and add clarifying comments
> - Fix latent issues
>
> V2 -> V3:
> - Address coccinelle warning
>
> V1 -> V2:
> - Add missing symbols
> - Resolve build warnings
> ---
> drivers/bluetooth/btrtl.c | 698 +++++++++++++++++++++++++++++++++++++-
> drivers/bluetooth/btrtl.h | 102 ++++++
> drivers/bluetooth/btusb.c | 3 +
> 3 files changed, 786 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c
> index 49ecb18fea45..450b32bcfa63 100644
> --- a/drivers/bluetooth/btrtl.c
> +++ b/drivers/bluetooth/btrtl.c
> @@ -22,6 +22,12 @@
> #define RTL_CHIP_8723CS_XX 5
> #define RTL_EPATCH_SIGNATURE "Realtech"
> #define RTL_EPATCH_SIGNATURE_V2 "RTBTCore"
> +#define RTL_EPATCH_SIGNATURE_V3 "BTNIC003"
> +#define RTL_PATCH_V3_1 0x01
> +#define RTL_PATCH_V3_2 0x02
> +#define IMAGE_ID_F000 0xf000
> +#define IMAGE_ID_F001 0xf001
> +#define IMAGE_ID_F002 0xf002
> #define RTL_ROM_LMP_8703B 0x8703
> #define RTL_ROM_LMP_8723A 0x1200
> #define RTL_ROM_LMP_8723B 0x8723
> @@ -33,7 +39,14 @@
> #define RTL_ROM_LMP_8922A 0x8922
> #define RTL_CONFIG_MAGIC 0x8723ab55
>
> -#define RTL_VSC_OP_COREDUMP 0xfcff
> +#define RTL_VSC_OP_DOWNLOAD_CMD 0xfc20
> +#define RTL_VSC_OP_READ_VENDER 0xfc61
> +#define RTL_VSC_OP_WRITE_VENDOR 0xfc62
> +#define RTL_VSC_OP_READ_ROM_VER 0xfc6d
> +#define RTL_VSC_OP_READ_CHIP_ID 0xfc6f
> +#define RTL_VSC_OP_COREDUMP 0xfcff
> +#define RTL_VSC_OP_CHECK_DOWNLOAD_STATE 0xfdcf
> +#define RTL_VSC_OP_WDG_RESET_CMD 0xfc8e
>
> #define IC_MATCH_FL_LMPSUBV (1 << 0)
> #define IC_MATCH_FL_HCIREV (1 << 1)
> @@ -50,12 +63,16 @@
>
> #define RTL_CHIP_SUBVER (&(struct rtl_vendor_cmd) {{0x10, 0x38, 0x04, 0x28, 0x80}})
> #define RTL_CHIP_REV (&(struct rtl_vendor_cmd) {{0x10, 0x3A, 0x04, 0x28, 0x80}})
> -#define RTL_SEC_PROJ (&(struct rtl_vendor_cmd) {{0x10, 0xA4, 0xAD, 0x00, 0xb0}})
> +#define RTL_SEC_PROJ_V2 (&(struct rtl_vendor_cmd) {{0x10, 0xA4, 0xAD, 0x00, 0xb0}})
> +#define RTL_SEC_PROJ_V3 (&(struct rtl_vendor_cmd) {{0x10, 0xA4, 0x0D, 0x01, 0xa0}})
>
> #define RTL_PATCH_SNIPPETS 0x01
> #define RTL_PATCH_DUMMY_HEADER 0x02
> #define RTL_PATCH_SECURITY_HEADER 0x03
>
> +#define CHIP_ID_V3_BASE 55
> +#define RTL_VENDOR_WRITE_TYPE 0x21
> +
> enum btrtl_chip_id {
> CHIP_ID_8723A,
> CHIP_ID_8723B,
> @@ -99,8 +116,11 @@ struct btrtl_device_info {
> int cfg_len;
> bool drop_fw;
> int project_id;
> + u32 opcode;
> + u8 fw_type;
> u8 key_id;
> struct list_head patch_subsecs;
> + struct list_head patch_images;
> };
>
> static const struct id_table ic_id_table[] = {
> @@ -371,6 +391,33 @@ static const struct id_table *btrtl_match_ic(u16 lmp_subver, u16 hci_rev,
> return &ic_id_table[i];
> }
>
> +static int btrtl_read_chip_id(struct hci_dev *hdev, u8 *chip_id)
> +{
> + struct rtl_rp_read_chip_id *rp;
> + struct sk_buff *skb;
> + int ret = 0;
> +
> + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_READ_CHIP_ID, 0, NULL, HCI_INIT_TIMEOUT);
> + if (IS_ERR(skb))
> + return PTR_ERR(skb);
> +
> + rp = skb_pull_data(skb, sizeof(*rp));
> + if (!rp) {
> + ret = -EIO;
> + goto out;
> + }
> +
> + rtl_dev_info(hdev, "chip_id status=0x%02x id=0x%02x",
> + rp->status, rp->chip_id);
> +
> + if (chip_id)
> + *chip_id = rp->chip_id;
> +
> +out:
> + kfree_skb(skb);
> + return ret;
> +}
> +
> static struct sk_buff *btrtl_read_local_version(struct hci_dev *hdev)
> {
> struct sk_buff *skb;
> @@ -397,8 +444,7 @@ static int rtl_read_rom_version(struct hci_dev *hdev, u8 *version)
> struct rtl_rom_version_evt *rom_version;
> struct sk_buff *skb;
>
> - /* Read RTL ROM version command */
> - skb = __hci_cmd_sync(hdev, 0xfc6d, 0, NULL, HCI_INIT_TIMEOUT);
> + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_READ_ROM_VER, 0, NULL, HCI_INIT_TIMEOUT);
> if (IS_ERR(skb)) {
> rtl_dev_err(hdev, "Read ROM version failed (%ld)",
> PTR_ERR(skb));
> @@ -427,7 +473,7 @@ static int btrtl_vendor_read_reg16(struct hci_dev *hdev,
> struct sk_buff *skb;
> int err = 0;
>
> - skb = __hci_cmd_sync(hdev, 0xfc61, sizeof(*cmd), cmd,
> + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_READ_VENDER, sizeof(*cmd), cmd,
> HCI_INIT_TIMEOUT);
> if (IS_ERR(skb)) {
> err = PTR_ERR(skb);
> @@ -449,6 +495,26 @@ static int btrtl_vendor_read_reg16(struct hci_dev *hdev,
> return 0;
> }
>
> +static int btrtl_vendor_write_mem(struct hci_dev *hdev, u32 addr, u32 val)
> +{
> + struct rtl_vendor_write_cmd cp;
> + struct sk_buff *skb;
> + int err = 0;
> +
> + cp.type = RTL_VENDOR_WRITE_TYPE;
> + cp.addr = cpu_to_le32(addr);
> + cp.val = cpu_to_le32(val);
> + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_WRITE_VENDOR, sizeof(cp), &cp, HCI_INIT_TIMEOUT);
> + if (IS_ERR(skb)) {
> + err = PTR_ERR(skb);
> + bt_dev_err(hdev, "RTL: Write mem32 failed (%d)", err);
> + return err;
> + }
> +
> + kfree_skb(skb);
> + return 0;
> +}
> +
> static void *rtl_iov_pull_data(struct rtl_iovec *iov, u32 len)
> {
> void *data = iov->data;
> @@ -462,6 +528,31 @@ static void *rtl_iov_pull_data(struct rtl_iovec *iov, u32 len)
> return data;
> }
>
> +
> +static void btrtl_insert_ordered_patch_image(struct rtl_section_patch_image *image,
> + struct btrtl_device_info *btrtl_dev)
> +{
> + struct list_head *pos;
> + struct list_head *next;
> + struct rtl_section_patch_image *node;
> +
> + list_for_each_safe(pos, next, &btrtl_dev->patch_images) {
> + node = list_entry(pos, struct rtl_section_patch_image, list);
> +
> + if (node->image_id > image->image_id) {
> + __list_add(&image->list, pos->prev, pos);
> + return;
> + }
> +
> + if (node->image_id == image->image_id &&
> + node->index > image->index) {
> + __list_add(&image->list, pos->prev, pos);
> + return;
> + }
> + }
> + __list_add(&image->list, pos->prev, pos);
> +}
> +
> static void btrtl_insert_ordered_subsec(struct rtl_subsection *node,
> struct btrtl_device_info *btrtl_dev)
> {
> @@ -633,6 +724,279 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev,
> }
>
> *_buf = ptr;
> + btrtl_dev->fw_type = FW_TYPE_V2;
> + return len;
> +}
> +
> +static int rtlbt_parse_config(struct hci_dev *hdev,
> + struct rtl_section_patch_image *patch_image,
> + struct btrtl_device_info *btrtl_dev)
> +{
> + const struct id_table *ic_info = NULL;
> + const struct firmware *fw;
> + char tmp_name[32];
> + char filename[64];
> + u8 *cfg_buf;
> + char *str;
> + char *p;
> + size_t len;
> + int ret;
> +
> + if (btrtl_dev && btrtl_dev->ic_info)
> + ic_info = btrtl_dev->ic_info;
> +
> + if (!ic_info)
> + return -EINVAL;
> +
> + str = ic_info->cfg_name;
> + if (btrtl_dev->fw_type == FW_TYPE_V3_1) {
> + if (!patch_image->image_id && !patch_image->index) {
> + snprintf(filename, sizeof(filename), "%s.bin", str);
> + goto load_fw;
> + }
> + goto done;
> + }
> +
> + len = strlen(str);
> + if (len > sizeof(tmp_name) - 1)
> + len = sizeof(tmp_name) - 1;
> + memcpy(tmp_name, str, len);
> + tmp_name[len] = '\0';
> +
> + str = tmp_name;
> + p = strsep(&str, ".");
> +
> + ret = snprintf(filename, sizeof(filename), "%s", p);
> + if (patch_image->config_rule && patch_image->need_config) {
> + switch (patch_image->image_id) {
> + case IMAGE_ID_F000:
> + case IMAGE_ID_F001:
> + case IMAGE_ID_F002:
> + ret += snprintf(filename + ret, sizeof(filename) - ret,
> + "_%04x", patch_image->image_id);
> + break;
> + default:
> + goto done;
> + }
> + } else {
> + goto done;
> + }
> +
> + snprintf(filename + ret, sizeof(filename) - ret, ".%s", str ? str : "bin");
> +
> +load_fw:
> + rtl_dev_info(hdev, "config file: %s", filename);
> + ret = request_firmware(&fw, filename, &hdev->dev);
> + if (ret < 0) {
> + if (btrtl_dev->fw_type == FW_TYPE_V3_2) {
> + len = 4;
> + cfg_buf = kvmalloc(len, GFP_KERNEL);
> + if (!cfg_buf)
> + return -ENOMEM;
> +
> + memset(cfg_buf, 0xff, len);
> + patch_image->cfg_buf = cfg_buf;
> + patch_image->cfg_len = len;
> + return 0;
> + }
> + goto err_req_fw;
> + }
> + rtl_dev_info(hdev, "config file: %s found", filename);
> + cfg_buf = kvmalloc(fw->size, GFP_KERNEL);
> + if (!cfg_buf) {
> + ret = -ENOMEM;
> + goto err;
> + }
> + memcpy(cfg_buf, fw->data, fw->size);
> + len = fw->size;
> + release_firmware(fw);
> +
> + patch_image->cfg_buf = cfg_buf;
> + patch_image->cfg_len = len;
> +done:
> + return 0;
> +err:
> + release_firmware(fw);
> +err_req_fw:
> + rtl_dev_info(hdev, "config file: [%s] not found", filename);
> + return ret;
> +}
> +
> +static int rtlbt_parse_section_v3(struct hci_dev *hdev,
> + struct btrtl_device_info *btrtl_dev,
> + u32 opcode, u8 *data, u32 len)
> +{
> + struct rtl_section_patch_image *patch_image;
> + struct rtl_patch_image_hdr *hdr;
> + u16 image_id;
> + u16 chip_id;
> + size_t patch_image_len;
> + u8 *ptr;
> + int ret = 0;
> + size_t i;
> + struct rtl_iovec iov = {
> + .data = data,
> + .len = len,
> + };
> +
> + hdr = rtl_iov_pull_data(&iov, sizeof(*hdr));
> + if (!hdr)
> + return -EINVAL;
> +
> + if (btrtl_dev->opcode && btrtl_dev->opcode != opcode) {
> + rtl_dev_err(hdev, "invalid opcode 0x%02x", opcode);
> + return -EINVAL;
> + }
> +
> + if (!btrtl_dev->opcode) {
> + btrtl_dev->opcode = opcode;
> + switch (btrtl_dev->opcode) {
> + case RTL_PATCH_V3_1:
> + btrtl_dev->fw_type = FW_TYPE_V3_1;
> + break;
> + case RTL_PATCH_V3_2:
> + btrtl_dev->fw_type = FW_TYPE_V3_2;
> + break;
> + default:
> + return -EINVAL;
> + }
> + }
> +
> + patch_image_len = (u32)le64_to_cpu(hdr->patch_image_len);
> + chip_id = le16_to_cpu(hdr->chip_id);
> + image_id = le16_to_cpu(hdr->image_id);
> + rtl_dev_info(hdev, "image (%04x:%02x), chip id %u, cut 0x%02x, len %08zx"
> + , image_id, hdr->index, chip_id, hdr->ic_cut,
> + patch_image_len);
> +
> + if (btrtl_dev->key_id != hdr->key_id) {
> + rtl_dev_err(hdev, "invalid key_id (%u, %u)", hdr->key_id,
> + btrtl_dev->key_id);
> + return -EINVAL;
> + }
> +
> + if (hdr->ic_cut != btrtl_dev->rom_version + 1) {
> + rtl_dev_info(hdev, "unused ic_cut (%u, %u)", hdr->ic_cut,
> + btrtl_dev->rom_version + 1);
> + return -EINVAL;
> + }
> +
> + if (btrtl_dev->fw_type == FW_TYPE_V3_1 && !btrtl_dev->project_id)
> + btrtl_dev->project_id = chip_id;
> +
> + if (btrtl_dev->fw_type == FW_TYPE_V3_2 &&
> + chip_id != btrtl_dev->project_id) {
> + rtl_dev_err(hdev, "invalid chip_id (%u, %d)", chip_id,
> + btrtl_dev->project_id);
> + return -EINVAL;
> + }
> +
> + ptr = rtl_iov_pull_data(&iov, patch_image_len);
> + if (!ptr)
> + return -ENODATA;
> +
> + patch_image = kzalloc_obj(*patch_image);
> + if (!patch_image)
> + return -ENOMEM;
> + patch_image->index = hdr->index;
> + patch_image->image_id = image_id;
> + patch_image->config_rule = hdr->config_rule;
> + patch_image->need_config = hdr->need_config;
> +
> + for (i = 0; i < DL_FIX_ADDR_MAX; i++) {
> + patch_image->fix[i].addr =
> + (u32)le64_to_cpu(hdr->addr_fix[i * 2]);
> + patch_image->fix[i].value =
> + (u32)le64_to_cpu(hdr->addr_fix[i * 2 + 1]);
> + }
> +
> + patch_image->image_len = patch_image_len;
> + patch_image->image_data = kvmalloc(patch_image_len, GFP_KERNEL);
> + if (!patch_image->image_data) {
> + ret = -ENOMEM;
> + goto err;
> + }
> + memcpy(patch_image->image_data, ptr, patch_image_len);
> + patch_image->image_ver =
> + get_unaligned_le32(ptr + patch_image->image_len - 4);
> + rtl_dev_info(hdev, "image version: %08x", patch_image->image_ver);
> +
> + rtlbt_parse_config(hdev, patch_image, btrtl_dev);
> +
> + ret = patch_image->image_len;
> +
> + btrtl_insert_ordered_patch_image(patch_image, btrtl_dev);
> +
> + return ret;
> +err:
> + kfree(patch_image);
> + return ret;
> +}
> +
> +static int rtlbt_parse_firmware_v3(struct hci_dev *hdev,
> + struct btrtl_device_info *btrtl_dev)
> +{
> + struct rtl_epatch_header_v3 *hdr;
> + int rc;
> + u32 num_sections;
> + struct rtl_section_v3 *section;
> + u32 section_len;
> + u32 opcode;
> + int len = 0;
> + int i;
> + u8 *ptr;
> + struct rtl_iovec iov = {
> + .data = btrtl_dev->fw_data,
> + .len = btrtl_dev->fw_len,
> + };
> +
> + rtl_dev_info(hdev, "key id %u", btrtl_dev->key_id);
> +
> + hdr = rtl_iov_pull_data(&iov, sizeof(*hdr));
> + if (!hdr)
> + return -EINVAL;
> + num_sections = le32_to_cpu(hdr->num_sections);
> +
> + rtl_dev_dbg(hdev, "timpstamp %08x-%08x", *((u32 *)hdr->timestamp),
> + *((u32 *)(hdr->timestamp + 4)));
> +
> + for (i = 0; i < num_sections; i++) {
> + section = rtl_iov_pull_data(&iov, sizeof(*section));
> + if (!section)
> + break;
> +
> + section_len = (u32)le64_to_cpu(section->len);
> + opcode = le32_to_cpu(section->opcode);
> +
> + rtl_dev_dbg(hdev, "opcode 0x%04x", section->opcode);
> +
> + ptr = rtl_iov_pull_data(&iov, section_len);
> + if (!ptr)
> + break;
> +
> + rc = 0;
> + switch (opcode) {
> + case RTL_PATCH_V3_1:
> + case RTL_PATCH_V3_2:
> + rc = rtlbt_parse_section_v3(hdev, btrtl_dev, opcode,
> + ptr, section_len);
> + break;
> + default:
> + rtl_dev_warn(hdev, "Unknown opcode %08x", opcode);
> + break;
> + }
> + if (rc < 0) {
> + rtl_dev_err(hdev, "Parse section (%u) err (%d)",
> + opcode, rc);
> + continue;
> + }
> + len += rc;
> + }
> +
> + rtl_dev_info(hdev, "image payload total len: 0x%08x", len);
> + if (!len)
> + return -ENODATA;
> +
> return len;
> }
>
> @@ -678,6 +1042,9 @@ static int rtlbt_parse_firmware(struct hci_dev *hdev,
> if (btrtl_dev->fw_len <= 8)
> return -EINVAL;
>
> + if (!memcmp(btrtl_dev->fw_data, RTL_EPATCH_SIGNATURE_V3, 8))
> + return rtlbt_parse_firmware_v3(hdev, btrtl_dev);
> +
> if (!memcmp(btrtl_dev->fw_data, RTL_EPATCH_SIGNATURE, 8))
> min_size = sizeof(struct rtl_epatch_header) +
> sizeof(extension_sig) + 3;
> @@ -813,10 +1180,11 @@ static int rtlbt_parse_firmware(struct hci_dev *hdev,
> memcpy(buf + patch_length - 4, &epatch_info->fw_version, 4);
>
> *_buf = buf;
> + btrtl_dev->fw_type = FW_TYPE_V1;
> return len;
> }
>
> -static int rtl_download_firmware(struct hci_dev *hdev,
> +static int rtl_download_firmware(struct hci_dev *hdev, u8 fw_type,
> const unsigned char *data, int fw_len)
> {
> struct rtl_download_cmd *dl_cmd;
> @@ -827,6 +1195,13 @@ static int rtl_download_firmware(struct hci_dev *hdev,
> int j = 0;
> struct sk_buff *skb;
> struct hci_rp_read_local_version *rp;
> + u8 dl_rp_len = sizeof(struct rtl_download_response);
> +
> + if (is_v3_fw(fw_type)) {
> + j = 1;
> + if (fw_type == FW_TYPE_V3_2)
> + dl_rp_len++;
> + }
>
> dl_cmd = kmalloc_obj(*dl_cmd);
> if (!dl_cmd)
> @@ -840,15 +1215,15 @@ static int rtl_download_firmware(struct hci_dev *hdev,
> j = 1;
>
> if (i == (frag_num - 1)) {
> - dl_cmd->index |= 0x80; /* data end */
> + if (!is_v3_fw(fw_type))
> + dl_cmd->index |= 0x80; /* data end */
> frag_len = fw_len % RTL_FRAG_LEN;
> }
> rtl_dev_dbg(hdev, "download fw (%d/%d). index = %d", i,
> frag_num, dl_cmd->index);
> memcpy(dl_cmd->data, data, frag_len);
>
> - /* Send download command */
> - skb = __hci_cmd_sync(hdev, 0xfc20, frag_len + 1, dl_cmd,
> + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_DOWNLOAD_CMD, frag_len + 1, dl_cmd,
> HCI_INIT_TIMEOUT);
> if (IS_ERR(skb)) {
> rtl_dev_err(hdev, "download fw command failed (%ld)",
> @@ -857,7 +1232,7 @@ static int rtl_download_firmware(struct hci_dev *hdev,
> goto out;
> }
>
> - if (skb->len != sizeof(struct rtl_download_response)) {
> + if (skb->len != dl_rp_len) {
> rtl_dev_err(hdev, "download fw event length mismatch");
> kfree_skb(skb);
> ret = -EIO;
> @@ -868,6 +1243,9 @@ static int rtl_download_firmware(struct hci_dev *hdev,
> data += RTL_FRAG_LEN;
> }
>
> + if (is_v3_fw(fw_type))
> + goto out;
> +
> skb = btrtl_read_local_version(hdev);
> if (IS_ERR(skb)) {
> ret = PTR_ERR(skb);
> @@ -885,6 +1263,237 @@ static int rtl_download_firmware(struct hci_dev *hdev,
> return ret;
> }
>
> +static int rtl_check_download_state(struct hci_dev *hdev,
> + struct btrtl_device_info *btrtl_dev)
> +{
> + struct sk_buff *skb;
> + int ret = 0;
> + u8 *state;
> +
> + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_CHECK_DOWNLOAD_STATE, 0, NULL, HCI_CMD_TIMEOUT);
> + if (IS_ERR(skb)) {
> + rtl_dev_err(hdev, "write tb error %lu", PTR_ERR(skb));
> + return -EIO;
> + }
> +
> + /* Other driver might be downloading the combined firmware. */
> + state = skb_pull_data(skb, sizeof(*state));
> + if (state && *state == 0x03) {
> + btrealtek_set_flag(hdev, REALTEK_DOWNLOADING);
> + ret = btrealtek_wait_on_flag_timeout(hdev, REALTEK_DOWNLOADING,
> + TASK_INTERRUPTIBLE,
> + msecs_to_jiffies(5000));
> + if (ret == -EINTR) {
> + bt_dev_err(hdev, "Firmware loading interrupted");
> + goto out;
> + }
> +
> + if (ret) {
> + bt_dev_err(hdev, "Firmware loading timeout");
> + ret = -ETIMEDOUT;
> + } else {
> + ret = -EALREADY;
> + }
> +
> + }
> +
> +out:
> + kfree_skb(skb);
> + return ret;
> +}
> +
> +static int rtl_finalize_download(struct hci_dev *hdev,
> + struct btrtl_device_info *btrtl_dev)
> +{
> + struct hci_rp_read_local_version *rp_ver;
> + u8 params[2] = { 0x03, 0xb2 };
> + struct sk_buff *skb;
> + int ret = 0;
> + u16 opcode;
> + u32 len;
> + u8 *p;
> +
> + opcode = RTL_VSC_OP_WDG_RESET_CMD;
> + len = 2;
> + if (btrtl_dev->opcode == RTL_PATCH_V3_1) {
> + opcode = RTL_VSC_OP_DOWNLOAD_CMD;
> + params[0] = 0x80;
> + len = 1;
> + }
> + skb = __hci_cmd_sync(hdev, opcode, len, params, HCI_CMD_TIMEOUT);
> + if (IS_ERR(skb)) {
> + rtl_dev_err(hdev, "Watchdog reset err (%ld)", PTR_ERR(skb));
> + return -EIO;
> + }
> + p = skb_pull_data(skb, 1);
> + if (!p) {
> + ret = -ENODATA;
> + goto out;
> + }
> + rtl_dev_info(hdev, "Watchdog reset status %02x", *p);
> + kfree_skb(skb);
> +
> + skb = btrtl_read_local_version(hdev);
> + if (IS_ERR(skb)) {
> + ret = PTR_ERR(skb);
> + rtl_dev_err(hdev, "read local version failed (%d)", ret);
> + return ret;
> + }
> +
> + rp_ver = skb_pull_data(skb, sizeof(*rp_ver));
> + if (rp_ver)
> + rtl_dev_info(hdev, "fw version 0x%04x%04x",
> + __le16_to_cpu(rp_ver->hci_rev),
> + __le16_to_cpu(rp_ver->lmp_subver));
> +out:
> + kfree_skb(skb);
> + return ret;
> +}
> +
> +static int rtl_security_check(struct hci_dev *hdev,
> + struct btrtl_device_info *btrtl_dev)
> +{
> + struct rtl_section_patch_image *tmp = NULL;
> + struct rtl_section_patch_image *image = NULL;
> + u32 val;
> + int ret;
> +
> + list_for_each_entry_reverse(tmp, &btrtl_dev->patch_images, list) {
> + /* Check security hdr */
> + if (!tmp->fix[DL_FIX_SEC_HDR_ADDR].value ||
> + !tmp->fix[DL_FIX_SEC_HDR_ADDR].addr ||
> + tmp->fix[DL_FIX_SEC_HDR_ADDR].addr == 0xffffffff)
> + continue;
> + rtl_dev_info(hdev, "addr 0x%08x, value 0x%08x",
> + tmp->fix[DL_FIX_SEC_HDR_ADDR].addr,
> + tmp->fix[DL_FIX_SEC_HDR_ADDR].value);
> + image = tmp;
> + break;
> + }
> +
> + if (!image)
> + return 0;
> +
> + rtl_dev_info(hdev, "sec image (%04x:%02x)", image->image_id,
> + image->index);
> + val = image->fix[DL_FIX_PATCH_ADDR].value + image->image_len -
> + image->fix[DL_FIX_SEC_HDR_ADDR].value;
> + ret = btrtl_vendor_write_mem(hdev, image->fix[DL_FIX_PATCH_ADDR].addr,
> + val);
> + if (ret) {
> + rtl_dev_err(hdev, "write sec reg failed (%d)", ret);
> + return ret;
> + }
> + return 0;
> +}
> +
> +static int rtl_download_firmware_v3(struct hci_dev *hdev,
> + struct btrtl_device_info *btrtl_dev)
> +{
> + struct rtl_section_patch_image *image, *tmp;
> + struct rtl_rp_dl_v3 *rp;
> + struct sk_buff *skb;
> + u8 *fw_data;
> + int fw_len;
> + int ret = 0;
> + u8 i;
> +
> + if (btrtl_dev->fw_type == FW_TYPE_V3_2) {
> + ret = rtl_check_download_state(hdev, btrtl_dev);
> + if (ret) {
> + if (ret == -EALREADY)
> + return 0;
> + return ret;
> + }
> + }
> +
> + list_for_each_entry_safe(image, tmp, &btrtl_dev->patch_images, list) {
> + rtl_dev_dbg(hdev, "image (%04x:%02x)", image->image_id,
> + image->index);
> +
> + for (i = DL_FIX_CI_ID; i < DL_FIX_ADDR_MAX; i++) {
> + if (!image->fix[i].addr ||
> + image->fix[i].addr == 0xffffffff) {
> + rtl_dev_dbg(hdev, "no need to write addr %08x",
> + image->fix[i].addr);
> + continue;
> + }
> + rtl_dev_dbg(hdev, "write addr and val, 0x%08x, 0x%08x",
> + image->fix[i].addr, image->fix[i].value);
> + if (btrtl_vendor_write_mem(hdev, image->fix[i].addr,
> + image->fix[i].value)) {
> + rtl_dev_err(hdev, "write reg failed");
> + ret = -EIO;
> + goto done;
> + }
> + }
> +
> + fw_len = image->image_len + image->cfg_len;
> + fw_data = kvmalloc(fw_len, GFP_KERNEL);
> + if (!fw_data) {
> + rtl_dev_err(hdev, "Couldn't alloc buf for image data");
> + ret = -ENOMEM;
> + goto done;
> + }
> + memcpy(fw_data, image->image_data, image->image_len);
> + if (image->cfg_len > 0)
> + memcpy(fw_data + image->image_len, image->cfg_buf,
> + image->cfg_len);
> +
> + rtl_dev_dbg(hdev, "patch image (%04x:%02x). len: %d",
> + image->image_id, image->index, fw_len);
> + rtl_dev_dbg(hdev, "fw_data %p, image buf %p, len %u", fw_data,
> + image->image_data, image->image_len);
> +
> + ret = rtl_download_firmware(hdev, btrtl_dev->fw_type, fw_data,
> + fw_len);
> + kvfree(fw_data);
> + if (ret < 0) {
> + rtl_dev_err(hdev, "download firmware failed (%d)", ret);
> + goto done;
> + }
> +
> + if (image->list.next != &btrtl_dev->patch_images &&
> + image->image_id == tmp->image_id)
> + continue;
> +
> + if (btrtl_dev->fw_type == FW_TYPE_V3_1)
> + continue;
> +
> + i = 0x80;
> + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_DOWNLOAD_CMD, 1, &i, HCI_CMD_TIMEOUT);
> + if (IS_ERR(skb)) {
> + ret = -EIO;
> + rtl_dev_err(hdev, "Failed to issue last cmd fc20, %ld",
> + PTR_ERR(skb));
> + goto done;
> + }
> + ret = 2;
> + rp = skb_pull_data(skb, sizeof(*rp));
> + if (rp)
> + ret = rp->err;
> + kfree_skb(skb);
> + if (ret == 2) {
> + /* Verification failure */
> + ret = -EFAULT;
> + goto done;
> + }
> + }
> +
> + if (btrtl_dev->fw_type == FW_TYPE_V3_1) {
> + ret = rtl_security_check(hdev, btrtl_dev);
> + if (ret) {
> + rtl_dev_err(hdev, "Security check failed (%d)", ret);
> + goto done;
> + }
> + }
> +
> + ret = rtl_finalize_download(hdev, btrtl_dev);
> +
> +done:
> + return ret;
> +}
> +
> static int rtl_load_file(struct hci_dev *hdev, const char *name, u8 **buff)
> {
> const struct firmware *fw;
> @@ -918,7 +1527,7 @@ static int btrtl_setup_rtl8723a(struct hci_dev *hdev,
> return -EINVAL;
> }
>
> - return rtl_download_firmware(hdev, btrtl_dev->fw_data,
> + return rtl_download_firmware(hdev, FW_TYPE_V0, btrtl_dev->fw_data,
> btrtl_dev->fw_len);
> }
>
> @@ -933,7 +1542,7 @@ static int btrtl_setup_rtl8723b(struct hci_dev *hdev,
> if (ret < 0)
> goto out;
>
> - if (btrtl_dev->cfg_len > 0) {
> + if (!is_v3_fw(btrtl_dev->fw_type) && btrtl_dev->cfg_len > 0) {
> tbuff = kvzalloc(ret + btrtl_dev->cfg_len, GFP_KERNEL);
> if (!tbuff) {
> ret = -ENOMEM;
> @@ -949,9 +1558,14 @@ static int btrtl_setup_rtl8723b(struct hci_dev *hdev,
> fw_data = tbuff;
> }
>
> + if (is_v3_fw(btrtl_dev->fw_type)) {
> + ret = rtl_download_firmware_v3(hdev, btrtl_dev);
> + goto out;
> + }
> +
> rtl_dev_info(hdev, "cfg_sz %d, total sz %d", btrtl_dev->cfg_len, ret);
>
> - ret = rtl_download_firmware(hdev, fw_data, ret);
> + ret = rtl_download_firmware(hdev, btrtl_dev->fw_type, fw_data, ret);
>
> out:
> kvfree(fw_data);
> @@ -1021,7 +1635,7 @@ static int rtl_read_chip_type(struct hci_dev *hdev, u8 *type)
> const unsigned char cmd_buf[] = {0x00, 0x94, 0xa0, 0x00, 0xb0};
>
> /* Read RTL chip type command */
> - skb = __hci_cmd_sync(hdev, 0xfc61, 5, cmd_buf, HCI_INIT_TIMEOUT);
> + skb = __hci_cmd_sync(hdev, RTL_VSC_OP_READ_VENDER, 5, cmd_buf, HCI_INIT_TIMEOUT);
> if (IS_ERR(skb)) {
> rtl_dev_err(hdev, "Read chip type failed (%ld)",
> PTR_ERR(skb));
> @@ -1047,6 +1661,7 @@ static int rtl_read_chip_type(struct hci_dev *hdev, u8 *type)
> void btrtl_free(struct btrtl_device_info *btrtl_dev)
> {
> struct rtl_subsection *entry, *tmp;
> + struct rtl_section_patch_image *image, *next;
>
> kvfree(btrtl_dev->fw_data);
> kvfree(btrtl_dev->cfg_data);
> @@ -1056,6 +1671,13 @@ void btrtl_free(struct btrtl_device_info *btrtl_dev)
> kfree(entry);
> }
>
> + list_for_each_entry_safe(image, next, &btrtl_dev->patch_images, list) {
> + list_del(&image->list);
> + kvfree(image->image_data);
> + kvfree(image->cfg_buf);
> + kfree(image);
> + }
> +
> kfree(btrtl_dev);
> }
> EXPORT_SYMBOL_GPL(btrtl_free);
> @@ -1063,7 +1685,7 @@ EXPORT_SYMBOL_GPL(btrtl_free);
> struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
> const char *postfix)
> {
> - struct btrealtek_data *coredump_info = hci_get_priv(hdev);
> + struct btrealtek_data *btrtl_data = hci_get_priv(hdev);
> struct btrtl_device_info *btrtl_dev;
> struct sk_buff *skb;
> struct hci_rp_read_local_version *resp;
> @@ -1072,6 +1694,7 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
> char cfg_name[40];
> u16 hci_rev, lmp_subver;
> u8 hci_ver, lmp_ver, chip_type = 0;
> + u8 chip_id = 0;
> int ret;
> int rc;
> u8 key_id;
> @@ -1084,8 +1707,15 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
> }
>
> INIT_LIST_HEAD(&btrtl_dev->patch_subsecs);
> + INIT_LIST_HEAD(&btrtl_dev->patch_images);
>
> check_version:
> + ret = btrtl_read_chip_id(hdev, &chip_id);
> + if (!ret && chip_id >= CHIP_ID_V3_BASE) {
> + btrtl_dev->project_id = chip_id;
> + goto read_local_ver;
> + }
> +
> ret = btrtl_vendor_read_reg16(hdev, RTL_CHIP_SUBVER, reg_val);
> if (ret < 0)
> goto err_free;
> @@ -1108,6 +1738,7 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
> }
> }
>
> +read_local_ver:
> skb = btrtl_read_local_version(hdev);
> if (IS_ERR(skb)) {
> ret = PTR_ERR(skb);
> @@ -1185,7 +1816,11 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
> goto err_free;
> }
>
> - rc = btrtl_vendor_read_reg16(hdev, RTL_SEC_PROJ, reg_val);
> + if (btrtl_dev->project_id >= CHIP_ID_V3_BASE)
> + rc = btrtl_vendor_read_reg16(hdev, RTL_SEC_PROJ_V3, reg_val);
> + else
> + rc = btrtl_vendor_read_reg16(hdev, RTL_SEC_PROJ_V2, reg_val);
> +
> if (rc < 0)
> goto err_free;
>
> @@ -1243,7 +1878,7 @@ struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
> hci_set_msft_opcode(hdev, 0xFCF0);
>
> if (btrtl_dev->ic_info)
> - coredump_info->rtl_dump.controller = btrtl_dev->ic_info->hw_info;
> + btrtl_data->rtl_dump.controller = btrtl_dev->ic_info->hw_info;
>
> return btrtl_dev;
>
> @@ -1416,6 +2051,35 @@ int btrtl_shutdown_realtek(struct hci_dev *hdev)
> }
> EXPORT_SYMBOL_GPL(btrtl_shutdown_realtek);
>
> +
> +int btrtl_recv_event(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> + struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC);
> + struct hci_event_hdr *hdr;
> + u8 *p;
> +
> + if (!clone)
> + goto out;
> +
> + hdr = skb_pull_data(clone, sizeof(*hdr));
> + if (!hdr || hdr->evt != HCI_VENDOR_PKT)
> + goto out;
> +
> + p = skb_pull_data(clone, 1);
> + if (!p)
> + goto out;
> + switch (*p) {
> + case 0x77:
> + if (btrealtek_test_and_clear_flag(hdev, REALTEK_DOWNLOADING))
> + btrealtek_wake_up_flag(hdev, REALTEK_DOWNLOADING);
> + break;
> + }
> +out:
> + consume_skb(clone);
> + return hci_recv_frame(hdev, skb);
> +}
> +EXPORT_SYMBOL_GPL(btrtl_recv_event);
> +
> static unsigned int btrtl_convert_baudrate(u32 device_baudrate)
> {
> switch (device_baudrate) {
> diff --git a/drivers/bluetooth/btrtl.h b/drivers/bluetooth/btrtl.h
> index a2d9d34f9fb0..dd4acdf29b2f 100644
> --- a/drivers/bluetooth/btrtl.h
> +++ b/drivers/bluetooth/btrtl.h
> @@ -12,6 +12,19 @@
> #define rtl_dev_info(dev, fmt, ...) bt_dev_info(dev, "RTL: " fmt, ##__VA_ARGS__)
> #define rtl_dev_dbg(dev, fmt, ...) bt_dev_dbg(dev, "RTL: " fmt, ##__VA_ARGS__)
>
> +#define FW_TYPE_V0 0
> +#define FW_TYPE_V1 1
> +#define FW_TYPE_V2 2
> +#define FW_TYPE_V3_1 3
> +#define FW_TYPE_V3_2 4
> +#define is_v3_fw(type) (type == FW_TYPE_V3_1 || type == FW_TYPE_V3_2)
> +
> +#define DL_FIX_CI_ID 0
> +#define DL_FIX_CI_ADDR 1
> +#define DL_FIX_PATCH_ADDR 2
> +#define DL_FIX_SEC_HDR_ADDR 3
> +#define DL_FIX_ADDR_MAX 4
> +
> struct btrtl_device_info;
>
> struct rtl_chip_type_evt {
> @@ -103,8 +116,79 @@ struct rtl_vendor_cmd {
> __u8 param[5];
> } __packed;
>
> +struct rtl_vendor_write_cmd {
> + u8 type;
> + __le32 addr;
> + __le32 val;
> +} __packed;
> +
> +struct rtl_rp_read_chip_id {
> + __u8 status;
> + __u8 chip_id;
> +} __packed;
> +
> +struct rtl_rp_dl_v3 {
> + __u8 status;
> + __u8 index;
> + __u8 err;
> +} __packed;
> +
> +struct rtl_epatch_header_v3 {
> + __u8 signature[8];
> + __u8 timestamp[8];
> + __le32 ver_rsvd;
> + __le32 num_sections;
> +} __packed;
> +
> +struct rtl_section_v3 {
> + __le32 opcode;
> + __le64 len;
> + u8 data[];
> +} __packed;
> +
> +struct rtl_addr_fix {
> + u32 addr;
> + u32 value;
> +};
> +
> +struct rtl_section_patch_image {
> + u16 image_id;
> + u8 index;
> + u8 config_rule;
> + u8 need_config;
> +
> + struct rtl_addr_fix fix[DL_FIX_ADDR_MAX];
> +
> + u32 image_len;
> + u8 *image_data;
> + u32 image_ver;
> +
> + u8 *cfg_buf;
> + u16 cfg_len;
> +
> + struct list_head list;
> +};
> +
> +struct rtl_patch_image_hdr {
> + __le16 chip_id;
> + u8 ic_cut;
> + u8 key_id;
> + u8 enable_ota;
> + __le16 image_id;
> + u8 config_rule;
> + u8 need_config;
> + u8 rsv[950];
> +
> + __le64 addr_fix[DL_FIX_ADDR_MAX * 2];
> + u8 index;
> +
> + __le64 patch_image_len;
> + __u8 data[];
> +} __packed;
> +
> enum {
> REALTEK_ALT6_CONTINUOUS_TX_CHIP,
> + REALTEK_DOWNLOADING,
>
> __REALTEK_NUM_FLAGS,
> };
> @@ -130,8 +214,20 @@ struct btrealtek_data {
> #define btrealtek_get_flag(hdev) \
> (((struct btrealtek_data *)hci_get_priv(hdev))->flags)
>
> +#define btrealtek_wake_up_flag(hdev, nr) \
> + do { \
> + struct btrealtek_data *rtl = hci_get_priv((hdev)); \
> + wake_up_bit(rtl->flags, (nr)); \
> + } while (0)
> +
> #define btrealtek_test_flag(hdev, nr) test_bit((nr), btrealtek_get_flag(hdev))
>
> +#define btrealtek_test_and_clear_flag(hdev, nr) \
> + test_and_clear_bit((nr), btrealtek_get_flag(hdev))
> +
> +#define btrealtek_wait_on_flag_timeout(hdev, nr, m, to) \
> + wait_on_bit_timeout(btrealtek_get_flag(hdev), (nr), m, to)
> +
> #if IS_ENABLED(CONFIG_BT_RTL)
>
> struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
> @@ -148,6 +244,7 @@ int btrtl_get_uart_settings(struct hci_dev *hdev,
> unsigned int *controller_baudrate,
> u32 *device_baudrate, bool *flow_control);
> void btrtl_set_driver_name(struct hci_dev *hdev, const char *driver_name);
> +int btrtl_recv_event(struct hci_dev *hdev, struct sk_buff *skb);
>
> #else
>
> @@ -157,6 +254,11 @@ static inline struct btrtl_device_info *btrtl_initialize(struct hci_dev *hdev,
> return ERR_PTR(-EOPNOTSUPP);
> }
>
> +static inline int btrtl_recv_event(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> static inline void btrtl_free(struct btrtl_device_info *btrtl_dev)
> {
> }
> diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
> index 7f14ce96319b..55a845efaeb5 100644
> --- a/drivers/bluetooth/btusb.c
> +++ b/drivers/bluetooth/btusb.c
> @@ -2798,6 +2798,9 @@ static int btusb_recv_event_realtek(struct hci_dev *hdev, struct sk_buff *skb)
> return 0;
> }
>
> + if (skb->data[0] == HCI_VENDOR_PKT)
> + return btrtl_recv_event(hdev, skb);
> +
> return hci_recv_frame(hdev, skb);
> }
>
> --
> 2.34.1
Sashiko flagged quite a few problems with these changes:
https://sashiko.dev/#/patchset/20260624053334.3151885-1-hildawu%40realtek.com
--
Luiz Augusto von Dentz
^ permalink raw reply
* Re: [PATCH v5 3/3] Bluetooth: btmtk: Add MT7928 support
From: Luiz Augusto von Dentz @ 2026-06-25 18:37 UTC (permalink / raw)
To: Chris Lu
Cc: Marcel Holtmann, Johan Hedberg, Sean Wang, Will Lee, SS Wu,
Steve Lee, linux-bluetooth, linux-kernel, linux-mediatek,
Paul Menzel
In-Reply-To: <20260624075505.1318804-4-chris.lu@mediatek.com>
Hi Chris,
On Wed, Jun 24, 2026 at 3:57 AM Chris Lu <chris.lu@mediatek.com> wrote:
>
> Add support for MT7928 (internal device ID is MT7935) which requires
> additional firmware (CBMCU firmware) loading before Bluetooth firmware.
>
> CBMCU is a new component on MT7928 to handle common part shared across
> the combo chip (Wi-Fi/Bluetooth's subsystem), providing a better user
> experience through improved coordination between subsystems.
>
> Implement two-phase CBMCU firmware download: Phase 1 loads section with
> type 0x5 containing global descriptor, section maps and signature data;
> Phase 2 loads remaining firmware sections. Add retry mechanism for
> concurrent download protection.
>
> After CBMCU firmware loads successfully, the driver continues to load
> corresponding BT firmware based on device ID through fallthrough to
> case 0x7922/0x7925.
>
> The firmware(CBMCU_CODE_MT7935_1_1.bin/BT_RAM_CODE_MT7935_1_1_hdr.bin)
> required for MT7928 will be scheduled for upload to linux-firmware at
> a later stage.
>
> MT7928 bring-up kernel log:
> [ 159.784050] usb 1-3: New USB device found, idVendor=0e8d, idProduct=7935, bcdDevice= 1.00
> [ 159.784107] usb 1-3: New USB device strings: Mfr=5, Product=6, SerialNumber=7
> [ 159.784140] usb 1-3: Product: Wireless_Device
> [ 159.784166] usb 1-3: Manufacturer: MediaTek Inc.
> [ 159.784192] usb 1-3: SerialNumber: 000000000
> [ 159.795736] Bluetooth: hci1: Loading CBMCU firmware: mediatek/mt7928/CBMCU_CODE_MT7935_1_1.bin
> [ 159.807197] Bluetooth: hci1: CBMCU HW ver: 0x7935, SW ver: 0x0000, Build Time: 20260601T161751+0800
> [ 160.123155] Bluetooth: hci1: CBMCU firmware download completed
> [ 160.143013] Bluetooth: hci1: Loading BT firmware: mediatek/mt7928/BT_RAM_CODE_MT7935_1_1_hdr.bin
> [ 160.152775] Bluetooth: hci1: BT HW ver: 0x7935, SW ver: 0x0000, Build Time: 20260527000816
> [ 163.242266] Bluetooth: hci1: Device setup in 3367430 usecs
> [ 163.242280] Bluetooth: hci1: HCI Enhanced Setup Synchronous Connection command is advertised, but not supported.
> [ 163.355900] Bluetooth: hci1: AOSP extensions version v2.00
> [ 163.355956] Bluetooth: hci1: AOSP quality report is supported
> [ 163.357902] Bluetooth: MGMT ver 1.23
>
> Signed-off-by: Chris Lu <chris.lu@mediatek.com>
> Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
> Reviewed-by: Sean Wang <sean.wang@mediatek.com>
> ---
> drivers/bluetooth/btmtk.c | 355 +++++++++++++++++++++++++++++++++++++-
> drivers/bluetooth/btmtk.h | 3 +
> 2 files changed, 357 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
> index dc40820bfea0..1b08db862c20 100644
> --- a/drivers/bluetooth/btmtk.c
> +++ b/drivers/bluetooth/btmtk.c
> @@ -21,6 +21,12 @@
> #define MTK_FW_ROM_PATCH_SEC_MAP_SIZE 64
> #define MTK_SEC_MAP_COMMON_SIZE 12
> #define MTK_SEC_MAP_NEED_SEND_SIZE 52
> +#define MTK_SEC_MAP_LENGTH_SIZE 4
> +#define MTK_SEC_CBMCU_DESC 0x5
> +
> +/* CBMCU WMT command flags */
> +#define BTMTK_CBMCU_FLAG_QUERY_STATUS 0xF0
> +#define BTMTK_CBMCU_FLAG_ENABLE_PATCH 0xF1
>
> /* It is for mt79xx iso data transmission setting */
> #define MTK_ISO_THRESHOLD 264
> @@ -120,6 +126,11 @@ void btmtk_fw_get_filename(char *buf, size_t size, u32 dev_id, u32 fw_ver,
> snprintf(buf, size,
> "mediatek/mt%04x/BT_RAM_CODE_MT%04x_1_%x_hdr.bin",
> dev_id & 0xffff, dev_id & 0xffff, (fw_ver & 0xff) + 1);
> + /* MT7928 */
> + else if (dev_id == 0x7935)
> + snprintf(buf, size,
> + "mediatek/mt7928/BT_RAM_CODE_MT%04x_1_1_hdr.bin",
> + dev_id & 0xffff);
> else if (dev_id == 0x7961 && fw_flavor)
> snprintf(buf, size,
> "mediatek/BT_RAM_CODE_MT%04x_1a_%x_hdr.bin",
> @@ -736,6 +747,7 @@ static int btmtk_usb_hci_wmt_sync(struct hci_dev *hdev,
> status = BTMTK_WMT_ON_UNDONE;
> break;
> case BTMTK_WMT_PATCH_DWNLD:
> + case BTMTK_WMT_CBMCU_DWNLD:
> if (wmt_evt->whdr.flag == 2)
> status = BTMTK_WMT_PATCH_DONE;
> else if (wmt_evt->whdr.flag == 1)
> @@ -872,6 +884,336 @@ static u32 btmtk_usb_reset_done(struct hci_dev *hdev)
> return val & MTK_BT_RST_DONE;
> }
>
> +static int btmtk_cbmcu_patch_status(struct hci_dev *hdev,
> + wmt_cmd_sync_func_t wmt_cmd_sync,
> + u8 *patch_status)
> +{
> + struct btmtk_hci_wmt_params wmt_params;
> + int status, err, retry = 20;
> +
> + do {
> + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD;
> + wmt_params.flag = BTMTK_CBMCU_FLAG_QUERY_STATUS;
> + wmt_params.dlen = 0;
> + wmt_params.data = NULL;
> + wmt_params.status = &status;
> +
> + err = wmt_cmd_sync(hdev, &wmt_params);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to query CBMCU patch status (%d)", err);
> + return err;
> + }
> +
> + *patch_status = (u8)status;
> +
> + if (*patch_status == BTMTK_WMT_PATCH_PROGRESS) {
> + msleep(100);
> + retry--;
> + } else {
> + break;
> + }
> + } while (retry > 0);
> +
> + return 0;
> +}
> +
> +static int btmtk_query_cbmcu_section(struct hci_dev *hdev,
> + wmt_cmd_sync_func_t wmt_cmd_sync,
> + u8 cbmcu_type,
> + const u8 *section_map,
> + u32 cert_len)
> +{
> + struct btmtk_hci_wmt_params wmt_params;
> + u8 cmd[64];
> + int status, err;
> +
> + cmd[0] = 0;
> + cmd[1] = cbmcu_type;
> +
> + if (cbmcu_type == 0)
> + put_unaligned_le32(cert_len, &cmd[2]);
> + else
> + memcpy(&cmd[2], section_map, MTK_SEC_MAP_NEED_SEND_SIZE);
> +
> + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD;
> + wmt_params.flag = 0;
> + wmt_params.dlen = cbmcu_type ?
> + MTK_SEC_MAP_NEED_SEND_SIZE + 2 :
> + MTK_SEC_MAP_LENGTH_SIZE + 2;
> + wmt_params.data = cmd;
> + wmt_params.status = &status;
> +
> + err = wmt_cmd_sync(hdev, &wmt_params);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to query CBMCU section (%d)", err);
> + return err;
> + }
> +
> + /* Query should return UNDONE status for successful section query */
> + if (status != BTMTK_WMT_PATCH_UNDONE) {
> + bt_dev_err(hdev, "CBMCU section query status error (%d)", status);
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static int btmtk_download_cbmcu_section(struct hci_dev *hdev,
> + wmt_cmd_sync_func_t wmt_cmd_sync,
> + const u8 *fw_data,
> + u32 dl_size)
> +{
> + struct btmtk_hci_wmt_params wmt_params;
> + u32 sent_len, total_size = dl_size;
> + int err;
> +
> + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD;
> + wmt_params.status = NULL;
> +
> + while (dl_size > 0) {
> + sent_len = min_t(u32, 250, dl_size);
> +
> + if (dl_size == total_size)
> + wmt_params.flag = BTMTK_WMT_PKT_START;
> + else if (dl_size == sent_len)
> + wmt_params.flag = BTMTK_WMT_PKT_END;
> + else
> + wmt_params.flag = BTMTK_WMT_PKT_CONTINUE;
> +
> + wmt_params.dlen = sent_len;
> + wmt_params.data = fw_data;
> +
> + err = wmt_cmd_sync(hdev, &wmt_params);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to send CBMCU section data (%d)", err);
> + return err;
> + }
> +
> + dl_size -= sent_len;
> + fw_data += sent_len;
> + }
> +
> + return 0;
> +}
> +
> +static int btmtk_enable_cbmcu_patch(struct hci_dev *hdev,
> + wmt_cmd_sync_func_t wmt_cmd_sync)
> +{
> + struct btmtk_hci_wmt_params wmt_params;
> + int err;
> +
> + wmt_params.op = BTMTK_WMT_CBMCU_DWNLD;
> + wmt_params.flag = BTMTK_CBMCU_FLAG_ENABLE_PATCH;
> + wmt_params.dlen = 0;
> + wmt_params.data = NULL;
> + wmt_params.status = NULL;
> +
> + err = wmt_cmd_sync(hdev, &wmt_params);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to enable CBMCU patch (%d)", err);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int btmtk_load_cbmcu_firmware(struct hci_dev *hdev,
> + const char *fwname,
> + wmt_cmd_sync_func_t wmt_cmd_sync,
> + u32 dev_id)
> +{
> + struct btmtk_patch_header *hdr;
> + struct btmtk_global_desc *globaldesc;
> + struct btmtk_section_map *sectionmap;
> + const struct firmware *fw;
> + const u8 *fw_ptr;
> + u8 *cert_buf = NULL;
> + u32 section_num, section_offset, dl_size, cert_len;
> + int i, err;
> +
> + err = request_firmware(&fw, fwname, &hdev->dev);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to load CBMCU firmware file %s (%d)",
> + fwname, err);
> + return err;
> + }
> +
> + if (fw->size < MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE) {
> + bt_dev_err(hdev, "CBMCU firmware too small: size=%zu, min=%u",
> + fw->size,
> + MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE);
> + err = -EINVAL;
> + goto err_release_fw;
> + }
> +
> + fw_ptr = fw->data;
> + hdr = (struct btmtk_patch_header *)fw_ptr;
> + globaldesc = (struct btmtk_global_desc *)(fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE);
> + section_num = le32_to_cpu(globaldesc->section_num);
> +
> + if (fw->size < MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE +
> + (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num) {
> + bt_dev_err(hdev, "CBMCU firmware truncated: size=%zu, expected=%zu (section_num=%u)",
> + fw->size,
> + MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE +
> + (size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num,
> + section_num);
> + err = -EINVAL;
> + goto err_release_fw;
> + }
> +
> + bt_dev_info(hdev, "CBMCU HW ver: 0x%04x, SW ver: 0x%04x, Build Time: %s",
> + dev_id & 0xffff, le16_to_cpu(hdr->swver), hdr->datetime);
> +
> + /* Phase 1: Download section type MTK_SEC_CBMCU_DESC */
> + for (i = 0; i < section_num; i++) {
> + sectionmap = (struct btmtk_section_map *)
> + (fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE +
> + MTK_FW_ROM_PATCH_GD_SIZE +
> + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * i);
> +
> + /* Only process MTK_SEC_CBMCU_DESC section in Phase 1 */
> + if ((le32_to_cpu(sectionmap->sectype) & 0xFFFF) != MTK_SEC_CBMCU_DESC)
> + continue;
> +
> + section_offset = le32_to_cpu(sectionmap->secoffset);
> + dl_size = le32_to_cpu(sectionmap->secsize);
> +
> + if (dl_size == 0)
> + continue;
> +
> + if (section_offset > fw->size ||
> + dl_size > fw->size - section_offset) {
> + bt_dev_err(hdev, "CBMCU Phase 1 section out of bounds");
> + err = -EINVAL;
> + goto err_release_fw;
> + }
> +
> + cert_len = MTK_FW_ROM_PATCH_GD_SIZE +
> + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num +
> + dl_size;
> +
> + /* Query cbmcu section */
> + err = btmtk_query_cbmcu_section(hdev, wmt_cmd_sync, 0, NULL,
> + cert_len);
> + if (err < 0)
> + goto err_release_fw;
> +
> + cert_buf = kmalloc(cert_len, GFP_KERNEL);
> + if (!cert_buf) {
> + err = -ENOMEM;
> + goto err_release_fw;
> + }
> +
> + /* Copy Global Descriptor + All Section Maps */
> + memcpy(cert_buf,
> + fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE,
> + MTK_FW_ROM_PATCH_GD_SIZE + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num);
> +
> + /* Copy Phase 1 section data */
> + memcpy(cert_buf + MTK_FW_ROM_PATCH_GD_SIZE +
> + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * section_num,
> + fw_ptr + section_offset,
> + dl_size);
> +
> + /* Download Phase 1 section */
> + err = btmtk_download_cbmcu_section(hdev, wmt_cmd_sync,
> + cert_buf, cert_len);
> + kfree(cert_buf);
> + cert_buf = NULL;
> +
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to download CBMCU Phase 1 section (%d)", err);
> + goto err_release_fw;
> + }
> +
> + break;
> + }
> +
> + /* Phase 2: Download other sections (type != MTK_SEC_CBMCU_DESC) */
> + for (i = 0; i < section_num; i++) {
> + sectionmap = (struct btmtk_section_map *)
> + (fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE +
> + MTK_FW_ROM_PATCH_GD_SIZE +
> + MTK_FW_ROM_PATCH_SEC_MAP_SIZE * i);
> +
> + /* Skip MTK_SEC_CBMCU_DESC section in Phase 2 */
> + if ((le32_to_cpu(sectionmap->sectype) & 0xFFFF) == MTK_SEC_CBMCU_DESC)
> + continue;
> +
> + section_offset = le32_to_cpu(sectionmap->secoffset);
> + dl_size = le32_to_cpu(sectionmap->bin_info_spec.dlsize);
> +
> + if (dl_size == 0)
> + continue;
> +
> + if (section_offset > fw->size ||
> + dl_size > fw->size - section_offset) {
> + bt_dev_err(hdev, "CBMCU Phase 2 section %d out of bounds", i);
> + err = -EINVAL;
> + goto err_release_fw;
> + }
> +
> + /* Query cbmcu section */
> + err = btmtk_query_cbmcu_section(hdev, wmt_cmd_sync, 1,
> + (u8 *)§ionmap->bin_info_spec,
> + 0);
> + if (err < 0)
> + goto err_release_fw;
> +
> + /* Download section data */
> + err = btmtk_download_cbmcu_section(hdev, wmt_cmd_sync,
> + fw_ptr + section_offset,
> + dl_size);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to download CBMCU section %d (%d)", i, err);
> + goto err_release_fw;
> + }
> + }
> +
> + bt_dev_info(hdev, "CBMCU firmware download completed");
> +
> +err_release_fw:
> + release_firmware(fw);
> + return err;
> +}
> +
> +static int btmtk_setup_cbmcu_firmware(struct hci_dev *hdev,
> + wmt_cmd_sync_func_t wmt_cmd_sync,
> + u32 dev_id)
> +{
> + char cbmcu_fwname[64];
> + u8 patch_status;
> + int err;
> +
> + err = btmtk_cbmcu_patch_status(hdev, wmt_cmd_sync, &patch_status);
> + if (err < 0)
> + return err;
> +
> + bt_dev_dbg(hdev, "CBMCU patch status: 0x%02x", patch_status);
> +
> + if (patch_status != BTMTK_WMT_PATCH_UNDONE)
> + return 0;
> +
> + snprintf(cbmcu_fwname, sizeof(cbmcu_fwname),
> + "mediatek/mt7928/CBMCU_CODE_MT%04x_1_1.bin",
> + dev_id & 0xffff);
> +
> + bt_dev_info(hdev, "Loading CBMCU firmware: %s", cbmcu_fwname);
> +
> + err = btmtk_load_cbmcu_firmware(hdev, cbmcu_fwname, wmt_cmd_sync, dev_id);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to download CBMCU firmware (%d)", err);
> + return err;
> + }
> +
> + err = btmtk_enable_cbmcu_patch(hdev, wmt_cmd_sync);
> + if (err < 0)
> + return err;
> +
> + return 0;
> +}
> +
> int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id)
> {
> u32 val;
> @@ -896,7 +1238,7 @@ int btmtk_usb_subsys_reset(struct hci_dev *hdev, u32 dev_id)
> if (err < 0)
> return err;
> msleep(100);
> - } else if (dev_id == 0x7925 || dev_id == 0x6639) {
> + } else if (dev_id == 0x7925 || dev_id == 0x6639 || dev_id == 0x7935) {
> err = btmtk_usb_uhw_reg_read(hdev, MTK_BT_RESET_REG_CONNV3, &val);
> if (err < 0)
> return err;
> @@ -1381,6 +1723,15 @@ int btmtk_usb_setup(struct hci_dev *hdev)
> case 0x7668:
> fwname = FIRMWARE_MT7668;
> break;
> + case 0x7935:
> + /* Requires CBMCU firmware before BT firmware */
> + err = btmtk_setup_cbmcu_firmware(hdev, btmtk_usb_hci_wmt_sync,
> + dev_id);
> + if (err < 0) {
> + bt_dev_err(hdev, "Failed to set up CBMCU firmware (%d)", err);
> + return err;
> + }
> + fallthrough;
> case 0x7922:
> case 0x7925:
> /*
> @@ -1598,3 +1949,5 @@ MODULE_FIRMWARE(FIRMWARE_MT7922);
> MODULE_FIRMWARE(FIRMWARE_MT7961);
> MODULE_FIRMWARE(FIRMWARE_MT7925);
> MODULE_FIRMWARE(FIRMWARE_MT7927);
> +MODULE_FIRMWARE(FIRMWARE_MT7928);
> +MODULE_FIRMWARE(FIRMWARE_MT7928_CBMCU);
> diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h
> index 51c18dde0a80..5fe4964b031b 100644
> --- a/drivers/bluetooth/btmtk.h
> +++ b/drivers/bluetooth/btmtk.h
> @@ -9,6 +9,8 @@
> #define FIRMWARE_MT7961 "mediatek/BT_RAM_CODE_MT7961_1_2_hdr.bin"
> #define FIRMWARE_MT7925 "mediatek/mt7925/BT_RAM_CODE_MT7925_1_1_hdr.bin"
> #define FIRMWARE_MT7927 "mediatek/mt7927/BT_RAM_CODE_MT6639_2_1_hdr.bin"
> +#define FIRMWARE_MT7928 "mediatek/mt7928/BT_RAM_CODE_MT7935_1_1_hdr.bin"
> +#define FIRMWARE_MT7928_CBMCU "mediatek/mt7928/CBMCU_CODE_MT7935_1_1.bin"
>
> #define HCI_EV_WMT 0xe4
> #define HCI_WMT_MAX_EVENT_SIZE 64
> @@ -54,6 +56,7 @@ enum {
> BTMTK_WMT_RST = 0x7,
> BTMTK_WMT_REGISTER = 0x8,
> BTMTK_WMT_SEMAPHORE = 0x17,
> + BTMTK_WMT_CBMCU_DWNLD = 0x58,
> };
>
> enum {
> --
> 2.45.2
Sashiko seems to have found quite a few problems with this set:
https://sashiko.dev/#/patchset/20260624075505.1318804-1-chris.lu%40mediatek.com
--
Luiz Augusto von Dentz
^ permalink raw reply
* RE: btmrvl: Fix hdev dangling pointer and error code in register_hdev
From: bluez.test.bot @ 2026-06-25 17:52 UTC (permalink / raw)
To: linux-bluetooth, vulab
In-Reply-To: <20260625160607.81615-1-vulab@iscas.ac.cn>
[-- Attachment #1: Type: text/plain, Size: 1348 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1116653
---Test result---
Test Summary:
CheckPatch PASS 1.04 seconds
VerifyFixes PASS 0.21 seconds
VerifySignedoff PASS 0.20 seconds
GitLint PASS 0.46 seconds
SubjectPrefix FAIL 0.20 seconds
BuildKernel PASS 26.32 seconds
CheckAllWarning PASS 29.01 seconds
CheckSparse PASS 27.79 seconds
BuildKernel32 PASS 25.65 seconds
CheckKernelLLVM SKIP 0.00 seconds
TestRunnerSetup PASS 490.64 seconds
IncrementalBuild PASS 25.84 seconds
Details
##############################
Test: SubjectPrefix - FAIL
Desc: Check subject contains "Bluetooth" prefix
Output:
"Bluetooth: " prefix is not specified in the subject
##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found
https://github.com/bluez/bluetooth-next/pull/353
---
Regards,
Linux Bluetooth
^ permalink raw reply
* RE: btintel_pcie: Clear hdev pointer in error path of btintel_pcie_setup_hdev
From: bluez.test.bot @ 2026-06-25 17:26 UTC (permalink / raw)
To: linux-bluetooth, vulab
In-Reply-To: <20260625155149.79838-1-vulab@iscas.ac.cn>
[-- Attachment #1: Type: text/plain, Size: 567 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/btintel_pcie.c:2478
error: drivers/bluetooth/btintel_pcie.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
* [PATCH] btmrvl: Fix hdev dangling pointer and error code in register_hdev
From: Wentao Liang @ 2026-06-25 16:06 UTC (permalink / raw)
To: marcel, luiz.dentz; +Cc: linux-bluetooth, linux-kernel, Wentao Liang, stable
In btmrvl_register_hdev(), when hci_register_dev() fails, the
function frees the hci_dev via hci_free_dev() but leaves
priv->btmrvl_dev.hcidev as a dangling pointer. While the subsequent
cleanup code does not currently access it, setting it to NULL is a
defensive fix that prevents potential use-after-free.
Additionally, the function always returns -ENOMEM on the
hci_register_dev() failure path, discarding the actual error code.
Fix this by preserving and returning the original error code.
Cc: stable@vger.kernel.org
Fixes: 132ff4e5fa8d ("Bluetooth: Add btmrvl driver for Marvell Bluetooth devices")
Signed-off-by: Wentao Liang <vulab@iscas.ac.cn>
---
drivers/bluetooth/btmrvl_main.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/bluetooth/btmrvl_main.c b/drivers/bluetooth/btmrvl_main.c
index d6f0ad0b4b6e..3a4c8abae05b 100644
--- a/drivers/bluetooth/btmrvl_main.c
+++ b/drivers/bluetooth/btmrvl_main.c
@@ -683,7 +683,7 @@ int btmrvl_register_hdev(struct btmrvl_private *priv)
ret = hci_register_dev(hdev);
if (ret < 0) {
BT_ERR("Can not register HCI device");
- goto err_hci_register_dev;
+ goto err_hci_register_dev_free;
}
#ifdef CONFIG_DEBUG_FS
@@ -692,8 +692,9 @@ int btmrvl_register_hdev(struct btmrvl_private *priv)
return 0;
-err_hci_register_dev:
+err_hci_register_dev_free:
hci_free_dev(hdev);
+ priv->btmrvl_dev.hcidev = NULL;
err_hdev:
/* Stop the thread servicing the interrupts */
@@ -702,7 +703,7 @@ int btmrvl_register_hdev(struct btmrvl_private *priv)
btmrvl_free_adapter(priv);
kfree(priv);
- return -ENOMEM;
+ return ret;
}
EXPORT_SYMBOL_GPL(btmrvl_register_hdev);
--
2.39.5 (Apple Git-154)
^ permalink raw reply related
* [PATCH] btintel_pcie: Clear hdev pointer in error path of btintel_pcie_setup_hdev
From: Wentao Liang @ 2026-06-25 15:51 UTC (permalink / raw)
To: marcel, luiz.dentz; +Cc: linux-bluetooth, linux-kernel, Wentao Liang, stable
In btintel_pcie_setup_hdev(), if hci_register_dev() fails, the
function frees the hci_dev via hci_free_dev() but leaves
data->hdev as a dangling pointer. If a subsequent error handler in
the probe function accesses data->hdev, it would result in a
use-after-free.
Although the current probe error path (btintel_pcie_reset_bt) does
not access data->hdev, setting it to NULL after freeing is a
defensive fix that prevents potential future bugs.
Cc: stable@vger.kernel.org
Fixes: 6e65a09f9275 ("Bluetooth: btintel_pcie: Add *setup* function to download firmware")
Signed-off-by: Wentao Liang <vulab@iscas.ac.cn>
---
drivers/bluetooth/btintel_pcie.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c
index 37e050763633..56d24467b7d5 100644
--- a/drivers/bluetooth/btintel_pcie.c
+++ b/drivers/bluetooth/btintel_pcie.c
@@ -2478,6 +2478,7 @@ static int btintel_pcie_setup_hdev(struct btintel_pcie_data *data)
exit_error:
hci_free_dev(hdev);
+ data->hdev = NULL;
return err;
}
--
2.39.5 (Apple Git-154)
^ permalink raw reply related
* RE: Add support for IPQ5018 Bluetooth
From: bluez.test.bot @ 2026-06-25 15:47 UTC (permalink / raw)
To: linux-bluetooth, george.moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-1-d999be0e04f7@outlook.com>
[-- Attachment #1: Type: text/plain, Size: 5640 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1116589
---Test result---
Test Summary:
CheckPatch FAIL 10.08 seconds
VerifyFixes PASS 0.27 seconds
VerifySignedoff PASS 0.31 seconds
GitLint FAIL 3.87 seconds
SubjectPrefix FAIL 1.71 seconds
BuildKernel PASS 25.31 seconds
CheckAllWarning PASS 27.86 seconds
CheckSparse PASS 28.46 seconds
BuildKernel32 PASS 24.83 seconds
CheckKernelLLVM SKIP 0.00 seconds
TestRunnerSetup PASS 459.56 seconds
TestRunner_l2cap-tester PASS 58.15 seconds
TestRunner_iso-tester PASS 74.50 seconds
TestRunner_bnep-tester PASS 19.39 seconds
TestRunner_mgmt-tester FAIL 211.62 seconds
TestRunner_rfcomm-tester PASS 25.51 seconds
TestRunner_sco-tester PASS 32.73 seconds
TestRunner_ioctl-tester PASS 25.67 seconds
TestRunner_mesh-tester FAIL 26.17 seconds
TestRunner_smp-tester PASS 23.93 seconds
TestRunner_userchan-tester PASS 19.86 seconds
TestRunner_6lowpan-tester PASS 22.74 seconds
IncrementalBuild PASS 37.70 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[1/6] dt-bindings: remoteproc: document M0 Bluetooth Subsystem secure PIL
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#108:
new file mode 100644
total: 0 errors, 1 warnings, 72 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/github/workspace/src/patch/14645041.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[2/6] remoteproc: qcom: Add M0 BTSS secure PIL driver
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#147:
new file mode 100644
total: 0 errors, 1 warnings, 286 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/github/workspace/src/patch/14645042.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[4/6] dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#106:
new file mode 100644
total: 0 errors, 1 warnings, 63 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/github/workspace/src/patch/14645044.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[5/6] Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#150:
new file mode 100644
total: 0 errors, 1 warnings, 963 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/github/workspace/src/patch/14645046.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[4/6] dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
1: T1 Title exceeds max length (81>80): "[4/6] dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller"
##############################
Test: SubjectPrefix - FAIL
Desc: Check subject contains "Bluetooth" prefix
Output:
"Bluetooth: " prefix is not specified in the subject
"Bluetooth: " prefix is not specified in the subject
"Bluetooth: " prefix is not specified in the subject
"Bluetooth: " prefix is not specified in the subject
##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found
##############################
Test: TestRunner_mgmt-tester - FAIL
Desc: Run mgmt-tester with test-runner
Output:
Total: 494, Passed: 488 (98.8%), Failed: 2, Not Run: 4
Failed Test Cases
Read Exp Feature - Success Failed 0.245 seconds
LL Privacy - Unpair 1 Timed out 1.861 seconds
##############################
Test: TestRunner_mesh-tester - FAIL
Desc: Run mesh-tester with test-runner
Output:
Total: 10, Passed: 8 (80.0%), Failed: 2, Not Run: 0
Failed Test Cases
Mesh - Send cancel - 1 Timed out 2.742 seconds
Mesh - Send cancel - 2 Timed out 1.989 seconds
https://github.com/bluez/bluetooth-next/pull/352
---
Regards,
Linux Bluetooth
^ permalink raw reply
* Re: [PATCH 2/6] remoteproc: qcom: Add M0 BTSS secure PIL driver
From: George Moussalem @ 2026-06-25 14:24 UTC (permalink / raw)
To: Philipp Zabel, Jens Axboe, Ulf Hansson, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Johannes Berg, Jeff Johnson,
Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
Balakrishna Godavarthi, Rocky Liao, Saravana Kannan, Andrew Lunn,
Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Bjorn Andersson,
Konrad Dybcio, Mathieu Poirier
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc
In-Reply-To: <439f76c3fcafdfb91cca426fcae17ef776776eab.camel@pengutronix.de>
Thanks, that was quick!
On 6/25/26 18:18, Philipp Zabel wrote:
> On Do, 2026-06-25 at 18:10 +0400, George Moussalem via B4 Relay wrote:
>> From: George Moussalem <george.moussalem@outlook.com>
>>
>> Add support to bring up the M0 core of the bluetooth subsystem found in
>> the IPQ5018 SoC.
>>
>> The signed firmware loaded is authenticated by TrustZone. If successful,
>> the M0 core boots the firmware and the peripheral is taken out of reset
>> using a Secure Channel Manager call to TrustZone.
>>
>> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
>> ---
>> drivers/remoteproc/Kconfig | 12 ++
>> drivers/remoteproc/Makefile | 1 +
>> drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++++++++++++++++++++++++++++++
>> 3 files changed, 274 insertions(+)
>>
> [...]
>> diff --git a/drivers/remoteproc/qcom_m0_btss_pil.c b/drivers/remoteproc/qcom_m0_btss_pil.c
>> new file mode 100644
>> index 000000000000..7168e270e4d4
>> --- /dev/null
>> +++ b/drivers/remoteproc/qcom_m0_btss_pil.c
>> @@ -0,0 +1,261 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) 2026 The Linux Foundation. All rights reserved.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/elf.h>
>> +#include <linux/firmware/qcom/qcom_scm.h>
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/of_reserved_mem.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/reset.h>
>> +#include <linux/soc/qcom/mdt_loader.h>
>> +
>> +#include "qcom_common.h"
>> +
>> +#define BTSS_PAS_ID 0xc
>> +
>> +struct m0_btss {
>> + struct device *dev;
>> + phys_addr_t mem_phys;
>> + phys_addr_t mem_reloc;
>> + void __iomem *mem_region;
>> + size_t mem_size;
>> + struct reset_control *btss_reset;
>
> Why is this stored here? It doesn't seem to be used.
will remove it and use devm_reset_control_get_exclusive_deasserted as
suggested below.
>
> [...]
>> +static int m0_btss_pil_probe(struct platform_device *pdev)
>> +{
>> + // struct reset_control *btss_reset;
>
> It looks like this shouldn't be commented out.
>
>> + struct device *dev = &pdev->dev;
>> + const char *fw_name = NULL;
>> + struct m0_btss *desc;
>> + struct clk *lpo_clk;
>> + struct rproc *rproc;
>> + int ret;
>> +
>> + ret = of_property_read_string(dev->of_node, "firmware-name",
>> + &fw_name);
>> + if (ret < 0)
>> + return ret;
>> +
>> + rproc = devm_rproc_alloc(dev, "m0btss", &m0_btss_ops,
>> + fw_name, sizeof(*desc));
>> + if (!rproc) {
>> + dev_err(dev, "failed to allocate rproc\n");
>> + return -ENOMEM;
>> + }
>> +
>> + desc = rproc->priv;
>> + desc->dev = dev;
>> +
>> + ret = m0_btss_alloc_memory_region(desc);
>> + if (ret)
>> + return ret;
>> +
>> + lpo_clk = devm_clk_get_enabled(dev, "btss_lpo_clk");
>> + if (IS_ERR(lpo_clk))
>> + return dev_err_probe(dev, PTR_ERR(lpo_clk),
>> + "Failed to get lpo clock\n");
>> +
>> + desc->btss_reset = devm_reset_control_get(dev, "btss_reset");
>
> Please use devm_reset_control_get_exclusive() directly.
>
>> + if (IS_ERR_OR_NULL(desc->btss_reset))
>> + return dev_err_probe(dev, PTR_ERR(desc->btss_reset),
>> + "unable to acquire btss_reset\n");
>> +
>> + ret = reset_control_deassert(desc->btss_reset);
>> + if (ret)
>> + return dev_err_probe(rproc->dev.parent, ret,
>> + "Failed to deassert reset\n");
>
> Shouldn't this be asserted on remove? Otherwise after an unbind/bind
> cycle probe will enable the clock with reset already deasserted.
> That may or may not be problematic.
>
> Consider using devm_reset_control_get_exclusive_deasserted().
>
>
> regards
> Philipp
Regards,
George
^ permalink raw reply
* Re: [PATCH 2/6] remoteproc: qcom: Add M0 BTSS secure PIL driver
From: Philipp Zabel @ 2026-06-25 14:18 UTC (permalink / raw)
To: george.moussalem, Jens Axboe, Ulf Hansson, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Johannes Berg, Jeff Johnson,
Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
Balakrishna Godavarthi, Rocky Liao, Saravana Kannan, Andrew Lunn,
Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Bjorn Andersson,
Konrad Dybcio, Mathieu Poirier
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc
In-Reply-To: <20260625-ipq5018-bluetooth-v1-2-d999be0e04f7@outlook.com>
On Do, 2026-06-25 at 18:10 +0400, George Moussalem via B4 Relay wrote:
> From: George Moussalem <george.moussalem@outlook.com>
>
> Add support to bring up the M0 core of the bluetooth subsystem found in
> the IPQ5018 SoC.
>
> The signed firmware loaded is authenticated by TrustZone. If successful,
> the M0 core boots the firmware and the peripheral is taken out of reset
> using a Secure Channel Manager call to TrustZone.
>
> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
> ---
> drivers/remoteproc/Kconfig | 12 ++
> drivers/remoteproc/Makefile | 1 +
> drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++++++++++++++++++++++++++++++
> 3 files changed, 274 insertions(+)
>
[...]
> diff --git a/drivers/remoteproc/qcom_m0_btss_pil.c b/drivers/remoteproc/qcom_m0_btss_pil.c
> new file mode 100644
> index 000000000000..7168e270e4d4
> --- /dev/null
> +++ b/drivers/remoteproc/qcom_m0_btss_pil.c
> @@ -0,0 +1,261 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2026 The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/elf.h>
> +#include <linux/firmware/qcom/qcom_scm.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/of_reserved_mem.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +#include <linux/soc/qcom/mdt_loader.h>
> +
> +#include "qcom_common.h"
> +
> +#define BTSS_PAS_ID 0xc
> +
> +struct m0_btss {
> + struct device *dev;
> + phys_addr_t mem_phys;
> + phys_addr_t mem_reloc;
> + void __iomem *mem_region;
> + size_t mem_size;
> + struct reset_control *btss_reset;
Why is this stored here? It doesn't seem to be used.
[...]
> +static int m0_btss_pil_probe(struct platform_device *pdev)
> +{
> + // struct reset_control *btss_reset;
It looks like this shouldn't be commented out.
> + struct device *dev = &pdev->dev;
> + const char *fw_name = NULL;
> + struct m0_btss *desc;
> + struct clk *lpo_clk;
> + struct rproc *rproc;
> + int ret;
> +
> + ret = of_property_read_string(dev->of_node, "firmware-name",
> + &fw_name);
> + if (ret < 0)
> + return ret;
> +
> + rproc = devm_rproc_alloc(dev, "m0btss", &m0_btss_ops,
> + fw_name, sizeof(*desc));
> + if (!rproc) {
> + dev_err(dev, "failed to allocate rproc\n");
> + return -ENOMEM;
> + }
> +
> + desc = rproc->priv;
> + desc->dev = dev;
> +
> + ret = m0_btss_alloc_memory_region(desc);
> + if (ret)
> + return ret;
> +
> + lpo_clk = devm_clk_get_enabled(dev, "btss_lpo_clk");
> + if (IS_ERR(lpo_clk))
> + return dev_err_probe(dev, PTR_ERR(lpo_clk),
> + "Failed to get lpo clock\n");
> +
> + desc->btss_reset = devm_reset_control_get(dev, "btss_reset");
Please use devm_reset_control_get_exclusive() directly.
> + if (IS_ERR_OR_NULL(desc->btss_reset))
> + return dev_err_probe(dev, PTR_ERR(desc->btss_reset),
> + "unable to acquire btss_reset\n");
> +
> + ret = reset_control_deassert(desc->btss_reset);
> + if (ret)
> + return dev_err_probe(rproc->dev.parent, ret,
> + "Failed to deassert reset\n");
Shouldn't this be asserted on remove? Otherwise after an unbind/bind
cycle probe will enable the clock with reset already deasserted.
That may or may not be problematic.
Consider using devm_reset_control_get_exclusive_deasserted().
regards
Philipp
^ permalink raw reply
* [PATCH 5/6] Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Add driver support for the Qualcomm IPQ5018 bluetooth chip.
The firmware runs on the M0 co-processor.
The host and the M0 core use a shared memory carveout for transport
using ring buffers. This driver implements the transport layer between
the HCI core and the Bluetooth subsystem running on the M0 core.
Notifications of host and M0 core events are triggered by an IPC
register BIT and an interrupt line respectfully.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
drivers/bluetooth/Kconfig | 11 +
drivers/bluetooth/Makefile | 1 +
drivers/bluetooth/btqcomipc.c | 939 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 951 insertions(+)
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 4e8c24d757e9..6b8bed6a6ffd 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -413,6 +413,17 @@ config BT_MTKUART
Say Y here to compile support for MediaTek Bluetooth UART devices
into the kernel or say M to compile it as module (btmtkuart).
+config BT_QCOMIPC
+ tristate "Qualcomm IPQ5018 IPC based HCI support"
+ select BT_QCA
+ help
+ Qualcomm IPQ5018 IPC based HCI driver.
+ This driver is used to bridge HCI data onto shared memory between
+ the host and the M0 BTSS core.
+
+ Say Y here to compile support for HCI over Qualcomm IPC into the
+ kernel or say M to compile as a module.
+
config BT_QCOMSMD
tristate "Qualcomm SMD based HCI support"
depends on RPMSG || (COMPILE_TEST && RPMSG=n)
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index e6b1c1180d1d..05f19047bed0 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_BT_MRVL) += btmrvl.o
obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
obj-$(CONFIG_BT_MTKSDIO) += btmtksdio.o
obj-$(CONFIG_BT_MTKUART) += btmtkuart.o
+obj-$(CONFIG_BT_QCOMIPC) += btqcomipc.o
obj-$(CONFIG_BT_QCOMSMD) += btqcomsmd.o
obj-$(CONFIG_BT_BCM) += btbcm.o
obj-$(CONFIG_BT_RTL) += btrtl.o
diff --git a/drivers/bluetooth/btqcomipc.c b/drivers/bluetooth/btqcomipc.c
new file mode 100644
index 000000000000..662a75b6c4a9
--- /dev/null
+++ b/drivers/bluetooth/btqcomipc.c
@@ -0,0 +1,939 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/remoteproc.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "btqca.h"
+
+/** Message header format.
+ *
+ * ----------------------------------------------------------------
+ * BitPos | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 - 0 |
+ * ---------------------------------------------------------------
+ * Field | long_msg |ACK | RFU | pkt_type | cmd |
+ * ----------------------------------------------------------------
+ *
+ * - long_msg : If set, indicates that the payload is larger than the
+ * IPC_MSG_PLD_SZ. The payload instead contains a pointer to the
+ * long message buffer in the shared BTSS memory space.
+ *
+ * - ACK : Set if sending ACK if required by sending acknowledegement
+ * to sender i.e. send an ack IPC interrupt if set.
+ *
+ * - RFU : Reserved for future use.
+ *
+ * - pkt_type : IPC Packet Type
+ *
+ * - cmd : Contains unique command ID
+ */
+
+#define IPC_MSG_HDR_SZ 4
+#define IPC_MSG_PLD_SZ 40
+#define IPC_TOTAL_MSG_SZ (IPC_MSG_HDR_SZ + IPC_MSG_PLD_SZ)
+
+/* Message Header */
+#define IPC_HDR_LONG_MSG BIT(15)
+#define IPC_HDR_REQ_ACK BIT(14)
+#define IPC_HDR_PKT_TYPE_MASK GENMASK(9, 8)
+#define IPC_HDR_PKT_TYPE_CUST 0
+#define IPC_HDR_PKT_TYPE_HCI 1
+#define IPC_HDR_PKT_TYPE_AUDIO 2
+#define IPC_HDR_PKT_TYPE_RFU 3
+#define IPC_HDR_CMD_MASK GENMASK(7, 0)
+
+#define IPC_CMD_STOP 1
+#define IPC_CMD_SWITCH_TO_UART 2
+#define IPC_CMD_PREPARE_DUMP 3
+#define IPC_CMD_COLLECT_DUMP 4
+#define IPC_CMD_START 5
+
+#define IPC_TX_QSIZE 32
+
+#define TO_APPS_ADDR(a) (desc->mem_region + (int)(uintptr_t)a)
+#define TO_BT_ADDR(a) (a - desc->mem_region)
+#define IPC_LBUF_SZ(w, x, y, z) (((TO_BT_ADDR((void *)w) + w->x) - w->y) / w->z)
+
+#define GET_NO_OF_BLOCKS(a, b) ((a + b - 1) / b)
+
+#define GET_RX_INDEX_FROM_BUF(x, y) ((x - desc->rx_ctxt->lring_buf) / y)
+
+#define GET_TX_INDEX_FROM_BUF(x, y) ((x - desc->tx_ctxt->lring_buf) / y)
+
+#define IS_RX_MEM_NON_CONTIGIOUS(buf, len, sz) \
+ ((buf + len) > (desc->rx_ctxt->lring_buf + \
+ (sz * desc->rx_ctxt->lmsg_buf_cnt)))
+
+#define POWER_CONTROL_DELAY_MS 50
+
+#define BTSS_PAS_ID 0xc
+
+struct long_msg_info {
+ __le16 smsg_free_cnt;
+ __le16 lmsg_free_cnt;
+ u8 ridx;
+ u8 widx;
+} __packed;
+
+struct ipc_aux_ptr {
+ __le32 len;
+ __le32 buf;
+} __packed;
+
+struct ring_buffer {
+ __le16 msg_hdr;
+ __le16 len;
+ union {
+ u8 smsg_data[IPC_MSG_PLD_SZ];
+ __le32 lmsg_data;
+ } payload;
+} __packed;
+
+struct ring_buffer_info {
+ __le32 rbuf;
+ u8 ring_buf_cnt;
+ u8 ridx;
+ u8 widx;
+ u8 tidx;
+ __le32 next;
+} __packed;
+
+struct context_info {
+ __le16 total_size;
+ u8 lmsg_buf_cnt;
+ u8 smsg_buf_cnt;
+ struct ring_buffer_info sring_buf_info;
+ __le32 sring_buf;
+ __le32 lring_buf;
+ __le32 reserved;
+} __packed;
+
+struct qcom_btss {
+ struct device *dev;
+ struct rproc *rproc;
+ struct hci_dev *hdev;
+
+ struct regmap *regmap;
+ u32 offset;
+ u32 bit;
+ int irq;
+
+ void *mem_region;
+ phys_addr_t mem_phys;
+ phys_addr_t mem_reloc;
+ size_t mem_size;
+
+ struct sk_buff_head tx_q;
+ struct workqueue_struct *wq;
+ struct work_struct work;
+ wait_queue_head_t wait_q;
+ spinlock_t lock;
+
+ struct context_info *tx_ctxt;
+ struct context_info *rx_ctxt;
+ struct long_msg_info lmsg_ctxt;
+
+ bool running;
+};
+
+static void btqcomipc_update_stats(struct hci_dev *hdev, struct sk_buff *skb);
+
+static void *btss_alloc_lmsg(struct qcom_btss *desc, u32 len,
+ struct ipc_aux_ptr *aux_ptr, bool *is_lbuf_full)
+{
+ struct device *dev = desc->dev;
+ u8 idx, blks, blks_consumed;
+ void *ret_ptr;
+ u32 lsz;
+
+ if (desc->tx_ctxt->lring_buf == 0) {
+ dev_err(dev, "no long message buffer initialized\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ lsz = IPC_LBUF_SZ(desc->tx_ctxt, total_size, lring_buf, lmsg_buf_cnt);
+ blks = GET_NO_OF_BLOCKS(len, lsz);
+
+ if (!desc->lmsg_ctxt.lmsg_free_cnt ||
+ (blks > desc->lmsg_ctxt.lmsg_free_cnt))
+ return ERR_PTR(-EAGAIN);
+
+ idx = desc->lmsg_ctxt.widx;
+
+ if ((desc->lmsg_ctxt.widx + blks) > desc->tx_ctxt->lmsg_buf_cnt) {
+ blks_consumed = desc->tx_ctxt->lmsg_buf_cnt - idx;
+ aux_ptr->len = len - (blks_consumed * lsz);
+ aux_ptr->buf = desc->tx_ctxt->lring_buf;
+ }
+
+ desc->lmsg_ctxt.widx = (desc->lmsg_ctxt.widx + blks) %
+ desc->tx_ctxt->lmsg_buf_cnt;
+
+ desc->lmsg_ctxt.lmsg_free_cnt -= blks;
+
+ if (desc->lmsg_ctxt.lmsg_free_cnt <=
+ ((desc->tx_ctxt->lmsg_buf_cnt * 20) / 100))
+ *is_lbuf_full = true;
+
+ ret_ptr = TO_APPS_ADDR(desc->tx_ctxt->lring_buf) + (idx * lsz);
+
+ return ret_ptr;
+}
+
+static struct ring_buffer_info *btss_get_tx_rbuf(struct qcom_btss *desc,
+ bool *is_sbuf_full)
+{
+ u8 idx;
+ struct ring_buffer_info *rinfo;
+
+ for (rinfo = &(desc->tx_ctxt->sring_buf_info); rinfo != NULL;
+ rinfo = (struct ring_buffer_info *)(uintptr_t)(rinfo->next)) {
+ idx = (rinfo->widx + 1) % (desc->tx_ctxt->smsg_buf_cnt);
+
+ if (idx != rinfo->tidx) {
+ desc->lmsg_ctxt.smsg_free_cnt--;
+
+ if (desc->lmsg_ctxt.smsg_free_cnt <=
+ ((desc->tx_ctxt->smsg_buf_cnt * 20) / 100))
+ *is_sbuf_full = true;
+
+ return rinfo;
+ }
+ }
+
+ return ERR_PTR(-EAGAIN);
+}
+
+static int btss_send(struct qcom_btss *desc, u16 msg_hdr,
+ struct sk_buff *skb)
+{
+ struct hci_dev *hdev = desc->hdev;
+ struct ring_buffer_info *rinfo;
+ struct ipc_aux_ptr aux_ptr;
+ struct ring_buffer *rbuf;
+ bool is_lbuf_full = false;
+ bool is_sbuf_full = false;
+ u16 hdr = msg_hdr;
+ void *ptr_buf;
+ u32 len;
+
+ /* Account for HCI packet type as it's not included in the skb payload */
+ len = skb->len + 1;
+ memset(&aux_ptr, 0, sizeof(struct ipc_aux_ptr));
+
+ if (len > IPC_MSG_PLD_SZ) {
+ hdr |= IPC_HDR_LONG_MSG;
+
+ ptr_buf = btss_alloc_lmsg(desc, len,
+ &aux_ptr, &is_lbuf_full);
+ if (IS_ERR(ptr_buf)) {
+ bt_dev_err(hdev, "long msg buf full");
+ hdev->stat.err_tx++;
+ return PTR_ERR(ptr_buf);
+ }
+ }
+
+ rinfo = btss_get_tx_rbuf(desc, &is_sbuf_full);
+ if (IS_ERR(rinfo)) {
+ bt_dev_err(hdev, "short msg buf full");
+ hdev->stat.err_tx++;
+ return PTR_ERR(rinfo);
+ }
+
+ rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(rinfo->rbuf)))[rinfo->widx];
+
+ if (len > IPC_MSG_PLD_SZ)
+ rbuf->payload.lmsg_data = cpu_to_le32(TO_BT_ADDR(ptr_buf));
+ else
+ ptr_buf = rbuf->payload.smsg_data;
+
+ /* if it's a short message, the aux len and buf are NULL */
+ memcpy_toio(ptr_buf, &hci_skb_pkt_type(skb), 1);
+ memcpy_toio((u8 *)ptr_buf + 1, skb->data, skb->len - aux_ptr.len);
+ if (aux_ptr.buf) {
+ memcpy_toio(TO_APPS_ADDR(aux_ptr.buf),
+ (skb->data + (skb->len - aux_ptr.len)), aux_ptr.len);
+ }
+
+ if (is_sbuf_full || is_lbuf_full)
+ hdr |= IPC_HDR_REQ_ACK;
+
+ rbuf->msg_hdr = cpu_to_le16(hdr);
+ rbuf->len = cpu_to_le16(len);
+
+ rinfo->widx = (rinfo->widx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+
+ regmap_set_bits(desc->regmap, desc->offset, BIT(desc->bit));
+
+ return 0;
+}
+
+static void btss_process_tx_queue(struct qcom_btss *desc)
+{
+ struct sk_buff *skb;
+ u16 hdr;
+ int ret;
+
+ while ((skb = skb_dequeue(&desc->tx_q))) {
+ hdr = FIELD_PREP(IPC_HDR_PKT_TYPE_MASK, IPC_HDR_PKT_TYPE_HCI);
+
+ ret = btss_send(desc, hdr, skb);
+ if (ret) {
+ bt_dev_err(desc->hdev, "Failed to send message");
+ skb_queue_head(&desc->tx_q, skb);
+ break;
+ }
+
+ btqcomipc_update_stats(desc->hdev, skb);
+ kfree_skb(skb);
+ }
+}
+
+static void btss_free_lmsg(struct qcom_btss *desc, u32 lmsg, u16 len)
+{
+ u8 idx;
+ u8 blks;
+ u32 lsz = IPC_LBUF_SZ(desc->tx_ctxt, total_size, lring_buf,
+ lmsg_buf_cnt);
+
+ idx = GET_TX_INDEX_FROM_BUF(lmsg, lsz);
+
+ if (idx != desc->lmsg_ctxt.ridx)
+ return;
+
+ blks = GET_NO_OF_BLOCKS(len, lsz);
+
+ desc->lmsg_ctxt.ridx = (desc->lmsg_ctxt.ridx + blks) %
+ desc->tx_ctxt->lmsg_buf_cnt;
+
+ desc->lmsg_ctxt.lmsg_free_cnt += blks;
+}
+
+static int btss_send_ctrl(struct qcom_btss *desc, u16 msg_hdr)
+{
+ struct ring_buffer_info *rinfo;
+ struct ring_buffer *rbuf;
+
+ rinfo = btss_get_tx_rbuf(desc, NULL);
+ if (IS_ERR(rinfo))
+ return PTR_ERR(rinfo);
+
+ rbuf = &((struct ring_buffer *)TO_APPS_ADDR(rinfo->rbuf))[rinfo->widx];
+ rbuf->msg_hdr = cpu_to_le16(msg_hdr);
+ rbuf->len = 0;
+
+ rinfo->widx = (rinfo->widx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+
+ regmap_set_bits(desc->regmap, desc->offset, BIT(desc->bit));
+
+ return 0;
+}
+
+static void btss_recv_cust_frame(struct qcom_btss *desc, u8 cmd)
+{
+ u16 msg_hdr = 0;
+ int ret;
+
+ msg_hdr |= cmd;
+
+ switch (cmd) {
+ case IPC_CMD_STOP:
+ bt_dev_info(desc->hdev, "BTSS stopped, gracefully stopping APSS IPC");
+ break;
+ case IPC_CMD_START:
+ desc->tx_ctxt = (struct context_info *)((void *)desc->rx_ctxt +
+ le16_to_cpu(desc->rx_ctxt->total_size));
+ desc->lmsg_ctxt.widx = 0;
+ desc->lmsg_ctxt.ridx = 0;
+ desc->lmsg_ctxt.smsg_free_cnt = desc->tx_ctxt->smsg_buf_cnt;
+ desc->lmsg_ctxt.lmsg_free_cnt = desc->tx_ctxt->lmsg_buf_cnt;
+ WRITE_ONCE(desc->running, true);
+ wake_up(&desc->wait_q);
+
+ bt_dev_info(desc->hdev, "BTSS started, initializing APSS BT IPC");
+ return;
+ default:
+ bt_dev_err(desc->hdev, "Unsupported CMD ID: %u", cmd);
+ return;
+ }
+
+ if (unlikely(!READ_ONCE(desc->running))) {
+ bt_dev_err(desc->hdev, "BTSS not initialized, no message sent");
+ return;
+ }
+
+ WRITE_ONCE(desc->running, false);
+
+ ret = btss_send_ctrl(desc, msg_hdr);
+ if (ret)
+ bt_dev_err(desc->hdev, "Failed to send control message");
+}
+
+static inline int btss_recv_hci_frame(struct qcom_btss *desc, const u8 *data, size_t len)
+{
+ unsigned char pkt_type;
+ struct sk_buff *skb;
+ size_t pkt_len;
+
+ if (len < 1)
+ return -EPROTO;
+
+ pkt_type = data[0];
+
+ switch (pkt_type) {
+ case HCI_EVENT_PKT:
+ {
+ if (len < HCI_EVENT_HDR_SIZE)
+ return -EILSEQ;
+ struct hci_event_hdr *hdr = (struct hci_event_hdr *)(data + 1);
+
+ pkt_len = HCI_EVENT_HDR_SIZE + hdr->plen;
+ break;
+ }
+ case HCI_COMMAND_PKT: {
+ if (len < HCI_COMMAND_HDR_SIZE)
+ return -EILSEQ;
+ struct hci_command_hdr *hdr = (struct hci_command_hdr *)(data + 1);
+
+ pkt_len = HCI_COMMAND_HDR_SIZE + le16_to_cpu(hdr->plen);
+ break;
+ }
+ case HCI_ACLDATA_PKT:
+ {
+ if (len < HCI_ACL_HDR_SIZE)
+ return -EILSEQ;
+ struct hci_acl_hdr *hdr = (struct hci_acl_hdr *)(data + 1);
+
+ pkt_len = HCI_ACL_HDR_SIZE + le16_to_cpu(hdr->dlen);
+ break;
+ }
+ case HCI_SCODATA_PKT:
+ {
+ if (len < HCI_SCO_HDR_SIZE)
+ return -EILSEQ;
+ struct hci_sco_hdr *hdr = (struct hci_sco_hdr *)(data + 1);
+
+ pkt_len = HCI_SCO_HDR_SIZE + hdr->dlen;
+ break;
+ }
+ default:
+ return -EPROTO;
+ }
+
+ if (pkt_len > len)
+ return -EINVAL;
+
+ skb = bt_skb_alloc(pkt_len, GFP_ATOMIC);
+ if (!skb) {
+ desc->hdev->stat.err_rx++;
+ return -ENOMEM;
+ }
+
+ skb->dev = (void *)desc->hdev;
+ hci_skb_pkt_type(skb) = pkt_type;
+ skb_put_data(skb, data + 1, pkt_len);
+
+ hci_recv_frame(desc->hdev, skb);
+ desc->hdev->stat.byte_rx += pkt_len;
+
+ return 0;
+}
+
+static inline int btss_process_rx(struct qcom_btss *desc,
+ struct ring_buffer_info *rinfo,
+ bool *ack, u8 *rx_count)
+{
+ u8 ridx, lbuf_idx, blks_consumed, pkt_type, cmd;
+ struct ipc_aux_ptr aux_ptr;
+ struct ring_buffer *rbuf;
+ uint8_t *rxbuf = NULL;
+ unsigned char *buf;
+ u32 lsz;
+ int ret;
+
+ ridx = rinfo->ridx;
+
+ while (ridx != rinfo->widx) {
+ memset(&aux_ptr, 0, sizeof(struct ipc_aux_ptr));
+
+ rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(rinfo->rbuf)))[ridx];
+
+ if (rbuf->msg_hdr & IPC_HDR_LONG_MSG) {
+ rxbuf = TO_APPS_ADDR(rbuf->payload.lmsg_data);
+ lsz = IPC_LBUF_SZ(desc->rx_ctxt, total_size, lring_buf,
+ lmsg_buf_cnt);
+
+ if (IS_RX_MEM_NON_CONTIGIOUS(rbuf->payload.lmsg_data,
+ rbuf->len, lsz)) {
+ lbuf_idx = GET_RX_INDEX_FROM_BUF(
+ rbuf->payload.lmsg_data, lsz);
+
+ blks_consumed = desc->rx_ctxt->lmsg_buf_cnt -
+ lbuf_idx;
+ aux_ptr.len = rbuf->len - (blks_consumed * lsz);
+ aux_ptr.buf = desc->rx_ctxt->lring_buf;
+ }
+ } else {
+ rxbuf = rbuf->payload.smsg_data;
+ }
+
+ *ack = (rbuf->msg_hdr & IPC_HDR_REQ_ACK);
+
+ pkt_type = FIELD_GET(IPC_HDR_PKT_TYPE_MASK, rbuf->msg_hdr);
+
+ switch (pkt_type) {
+ case IPC_HDR_PKT_TYPE_HCI:
+ buf = kmalloc(rbuf->len, GFP_ATOMIC);
+ if (!buf) {
+ rinfo->ridx = ridx;
+ return -ENOMEM;
+ }
+
+ memcpy_fromio(buf, rxbuf, rbuf->len - aux_ptr.len);
+
+ if (aux_ptr.buf)
+ memcpy_fromio(buf + (rbuf->len - aux_ptr.len),
+ TO_APPS_ADDR(aux_ptr.buf), aux_ptr.len);
+
+ ret = btss_recv_hci_frame(desc, buf, rbuf->len);
+ if (ret)
+ bt_dev_err(desc->hdev, "Failed to process HCI frame: %d", ret);
+ kfree(buf);
+ break;
+ case IPC_HDR_PKT_TYPE_CUST:
+ cmd = FIELD_GET(IPC_HDR_CMD_MASK, rbuf->msg_hdr);
+ btss_recv_cust_frame(desc, cmd);
+ break;
+ default:
+ break;
+ }
+
+ ridx = (ridx + 1) % rinfo->ring_buf_cnt;
+
+ if (rx_count)
+ (*rx_count)++;
+
+ rinfo->ridx = ridx;
+ }
+
+ return 0;
+}
+
+static void btss_process_ack(struct qcom_btss *desc)
+{
+ struct ring_buffer_info *rinfo;
+ struct ring_buffer *rbuf;
+ u8 tidx;
+
+ for (rinfo = &desc->tx_ctxt->sring_buf_info; rinfo != NULL;
+ rinfo = (struct ring_buffer_info *)(uintptr_t)(rinfo->next)) {
+ tidx = rinfo->tidx;
+ rbuf = (struct ring_buffer *)TO_APPS_ADDR(rinfo->rbuf);
+
+ while (tidx != rinfo->ridx) {
+ if (rbuf[tidx].msg_hdr & IPC_HDR_LONG_MSG) {
+ btss_free_lmsg(desc,
+ rbuf[tidx].payload.lmsg_data,
+ rbuf[tidx].len);
+ }
+
+ tidx = (tidx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+ desc->lmsg_ctxt.smsg_free_cnt++;
+ }
+
+ rinfo->tidx = tidx;
+
+ btss_process_tx_queue(desc);
+ }
+}
+
+static void btss_worker(struct work_struct *work)
+{
+ struct qcom_btss *desc = container_of(work, struct qcom_btss, work);
+ struct ring_buffer_info *rinfo;
+ bool ack = false;
+ u32 offset;
+ int ret;
+
+ if (desc->rproc->state != RPROC_RUNNING)
+ return;
+
+ spin_lock(&desc->lock);
+
+ if (unlikely(!READ_ONCE(desc->running))) {
+ // FW sets offset of RX context info at start of memory region upon boot
+ offset = readl(desc->mem_region);
+ dev_dbg(desc->dev, "offset after M0 boot: 0x%08x\n", offset);
+ desc->rx_ctxt = (struct context_info *)(desc->mem_region + offset);
+ } else {
+ btss_process_ack(desc);
+ }
+
+ for (rinfo = &(desc->rx_ctxt->sring_buf_info);
+ rinfo != NULL;
+ rinfo = (struct ring_buffer_info *)(uintptr_t)(rinfo->next)) {
+ ret = btss_process_rx(desc, rinfo, &ack,
+ &desc->rx_ctxt->smsg_buf_cnt);
+ if (ret) {
+ bt_dev_err(desc->hdev, "Failed to process peer msgs: %d", ret);
+ goto spin_unlock;
+ }
+ }
+
+ if (ack)
+ regmap_set_bits(desc->regmap, desc->offset, BIT(desc->bit));
+
+spin_unlock:
+ spin_unlock(&desc->lock);
+}
+
+static irqreturn_t btss_irq_handler(int irq, void *data)
+{
+ struct qcom_btss *desc = data;
+
+ queue_work(desc->wq, &desc->work);
+
+ return IRQ_HANDLED;
+}
+
+static int btss_init(struct qcom_btss *desc)
+{
+ struct device *dev = desc->dev;
+ int ret;
+
+ init_waitqueue_head(&desc->wait_q);
+ spin_lock_init(&desc->lock);
+ skb_queue_head_init(&desc->tx_q);
+
+ desc->wq = create_singlethread_workqueue("btss_wq");
+ if (!desc->wq) {
+ dev_err(dev, "Failed to initialize workqueue\n");
+ return -EAGAIN;
+ }
+
+ INIT_WORK(&desc->work, btss_worker);
+
+ ret = devm_request_threaded_irq(dev, desc->irq, NULL, btss_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "btss_irq", desc);
+
+ if (ret)
+ dev_err(dev, "error registering irq[%d] ret = %d\n",
+ desc->irq, ret);
+
+ return ret;
+}
+
+static void btqcomipc_update_stats(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ u8 pkt_type = hci_skb_pkt_type(skb);
+
+ hdev->stat.byte_tx += skb->len;
+ switch (pkt_type) {
+ case HCI_COMMAND_PKT:
+ hdev->stat.cmd_tx++;
+ break;
+ case HCI_ACLDATA_PKT:
+ hdev->stat.acl_tx++;
+ break;
+ case HCI_SCODATA_PKT:
+ hdev->stat.sco_tx++;
+ break;
+ default:
+ break;
+ }
+}
+
+static int btqcomipc_open(struct hci_dev *hdev)
+{
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+ int ret;
+
+ ret = btss_init(desc);
+ if (ret) {
+ bt_dev_err(hdev, "Failed initializing BTSS: %d", ret);
+ return ret;
+ }
+
+ /* wait 2 seconds for filesystem to become available */
+ msleep(2000);
+
+ /* Boot M0 firmware */
+ ret = rproc_boot(desc->rproc);
+ if (ret) {
+ bt_dev_err(hdev, "Failed to boot M0 processor: %d", ret);
+ return ret;
+ }
+
+ msleep(POWER_CONTROL_DELAY_MS);
+ ret = wait_event_timeout(desc->wait_q, READ_ONCE(desc->running),
+ msecs_to_jiffies(1000));
+
+ if (!ret) {
+ bt_dev_err(hdev, "Timeout waiting for BTSS start");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int btqcomipc_close(struct hci_dev *hdev)
+{
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+ rproc_shutdown(desc->rproc);
+ msleep(POWER_CONTROL_DELAY_MS);
+
+ return 0;
+}
+
+static int btqcomipc_shutdown(struct hci_dev *hdev)
+{
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+ WRITE_ONCE(desc->running, false);
+ if (desc->wq != NULL) {
+ disable_irq(desc->irq);
+ flush_workqueue(desc->wq);
+ devm_free_irq(desc->dev, desc->irq, desc);
+ skb_queue_purge(&desc->tx_q);
+ destroy_workqueue(desc->wq);
+ desc->wq = NULL;
+ }
+
+ return 0;
+}
+
+static int btqcomipc_setup(struct hci_dev *hdev)
+{
+ struct qca_btsoc_version ver;
+ int ret;
+
+ /*
+ * Enable controller to do both LE scan and BR/EDR inquiry
+ * simultaneously.
+ */
+ hci_set_quirk(hdev, HCI_QUIRK_SIMULTANEOUS_DISCOVERY);
+
+ /*
+ * Enable NON_PERSISTENT_SETUP QUIRK to ensure to execute
+ * setup for every hci up.
+ */
+ hci_set_quirk(hdev, HCI_QUIRK_NON_PERSISTENT_SETUP);
+ ret = qca_read_soc_version(hdev, &ver, QCA_IPQ5018);
+ if (ret)
+ return -EINVAL;
+
+ ret = qca_uart_setup(hdev, 0, QCA_IPQ5018, ver, NULL);
+ if (ret) {
+ bt_dev_err(hdev, "Failed to setup UART: %d\n", ret);
+ return ret;
+ }
+
+ bt_dev_info(hdev, "QCA Build Info: %s", hdev->fw_info);
+
+ /* Obtain and set BD address from NVMEM cell */
+ hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM);
+ hci_set_quirk(hdev, HCI_QUIRK_BDADDR_NVMEM_BE);
+
+ return 0;
+}
+
+static int btqcomipc_send(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ u16 hdr = FIELD_PREP(IPC_HDR_PKT_TYPE_MASK, IPC_HDR_PKT_TYPE_HCI);
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+ int ret;
+
+ if (unlikely(!READ_ONCE(desc->running))) {
+ bt_dev_err(hdev, "BTSS not initialized, failed to send message");
+ ret = -ENODEV;
+ goto free_skb;
+ }
+
+ ret = btss_send(desc, hdr, skb);
+ if (ret) {
+ if (ret == -EAGAIN) {
+ if (skb_queue_len(&desc->tx_q) >= IPC_TX_QSIZE) {
+ bt_dev_err(hdev, "TX queue full, dropping message");
+ hdev->stat.err_tx++;
+ ret = -ENOBUFS;
+ } else {
+ skb_queue_tail(&desc->tx_q, skb);
+ return 0;
+ }
+ } else {
+ bt_dev_err(hdev, "Failed to send message: %d", ret);
+ hdev->stat.err_tx++;
+ }
+ }
+
+ btqcomipc_update_stats(desc->hdev, skb);
+
+free_skb:
+ kfree_skb(skb);
+
+ return ret;
+}
+
+static int btqcomipc_flush(struct hci_dev *hdev)
+{
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+ skb_queue_purge(&desc->tx_q);
+ return 0;
+}
+
+static int btqcomipc_alloc_memory_region(struct qcom_btss *desc)
+{
+ struct device *dev = desc->dev;
+ struct resource res;
+ int ret;
+
+ /* lookup reserved-memory region of the remoteproc node */
+ ret = of_reserved_mem_region_to_resource(desc->rproc->dev.parent->of_node, 0, &res);
+ if (ret) {
+ dev_err(dev, "unable to acquire memory-region resource\n");
+ return ret;
+ }
+
+ desc->mem_phys = res.start;
+ desc->mem_reloc = res.start;
+ desc->mem_size = resource_size(&res);
+ desc->mem_region = devm_ioremap(dev, desc->mem_phys, desc->mem_size);
+ if (!desc->mem_region) {
+ dev_err(dev, "unable to map memory region: %pR\n", &res);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void btqcomipc_rproc_put(void *data)
+{
+ struct rproc *rproc = data;
+
+ rproc_put(rproc);
+}
+
+static int btqcomipc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct qcom_btss *desc;
+ phandle rproc_phandle;
+ struct hci_dev *hdev;
+ unsigned int args[2];
+ int ret;
+
+ desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ return -ENOMEM;
+
+ desc->dev = dev;
+
+ if (of_property_read_u32(dev->of_node, "qcom,rproc", &rproc_phandle))
+ return dev_err_probe(dev, -ENOENT, "Failed to get remoteproc handle\n");
+
+ desc->rproc = rproc_get_by_phandle(rproc_phandle);
+ if (!desc->rproc)
+ return dev_err_probe(dev, -EPROBE_DEFER, "Failed to get remoteproc\n");
+
+ devm_add_action_or_reset(dev, btqcomipc_rproc_put, desc->rproc);
+
+ ret = btqcomipc_alloc_memory_region(desc);
+ if (ret)
+ return ret;
+
+ desc->regmap = syscon_regmap_lookup_by_phandle_args(dev->of_node, "qcom,ipc", 2, args);
+ if (IS_ERR(desc->regmap))
+ return PTR_ERR(desc->regmap);
+
+ desc->offset = args[0];
+ desc->bit = args[1];
+
+ desc->irq = platform_get_irq(pdev, 0);
+ if (desc->irq < 0)
+ return dev_err_probe(dev, desc->irq, "Failed to acquire IRQ\n");
+
+ hdev = hci_alloc_dev();
+ if (!hdev)
+ return -ENOMEM;
+
+ hci_set_drvdata(hdev, desc);
+ desc->hdev = hdev;
+ SET_HCIDEV_DEV(hdev, &pdev->dev);
+ hdev->bus = HCI_IPC;
+
+ hdev->open = btqcomipc_open;
+ hdev->close = btqcomipc_close;
+ hdev->shutdown = btqcomipc_shutdown;
+ hdev->setup = btqcomipc_setup;
+ hdev->send = btqcomipc_send;
+ hdev->flush = btqcomipc_flush;
+ hdev->set_bdaddr = qca_set_bdaddr;
+
+ ret = hci_register_dev(hdev);
+ if (ret < 0) {
+ hci_free_dev(hdev);
+ return dev_err_probe(dev, -EBUSY, "Failed to register hdev\n");
+ }
+
+ platform_set_drvdata(pdev, desc);
+
+ return 0;
+}
+
+static void btqcomipc_remove(struct platform_device *pdev)
+{
+ struct qcom_btss *desc = platform_get_drvdata(pdev);
+
+ if (!desc || !desc->hdev)
+ return;
+
+ hci_unregister_dev(desc->hdev);
+ hci_free_dev(desc->hdev);
+}
+
+static const struct of_device_id btqcomipc_of_match[] = {
+ { .compatible = "qcom,ipq5018-bt" },
+ { /* sentinel */},
+};
+MODULE_DEVICE_TABLE(of, btqcomipc_of_match);
+
+static struct platform_driver btqcomipc_driver = {
+ .probe = btqcomipc_probe,
+ .remove = btqcomipc_remove,
+ .driver = {
+ .name = "btqcomipc",
+ .of_match_table = btqcomipc_of_match,
+ },
+};
+
+module_platform_driver(btqcomipc_driver);
+
+MODULE_DESCRIPTION("Qualcomm Bluetooth IPC Driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH 6/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Add nodes for the M0 remoteproc, reserved memory carveout, and Bluetooth
to bring up the M0 core and enable the Bluetooth Subsystem.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
arch/arm64/boot/dts/qcom/ipq5018.dtsi | 34 +++++++++++++++++++++++++++++++++-
1 file changed, 33 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/qcom/ipq5018.dtsi b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
index 6f8004a22a1f..4fdf20c87b0a 100644
--- a/arch/arm64/boot/dts/qcom/ipq5018.dtsi
+++ b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
@@ -17,6 +17,17 @@ / {
#address-cells = <2>;
#size-cells = <2>;
+ bluetooth: bluetooth {
+ compatible = "qcom,ipq5018-bt";
+
+ qcom,ipc = <&apcs_glb 8 23>;
+ interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+ qcom,rproc = <&m0_btss>;
+
+ status = "disabled";
+ };
+
clocks {
gephy_rx_clk: gephy-rx-clk {
compatible = "fixed-clock";
@@ -131,11 +142,31 @@ psci {
method = "smc";
};
+ m0_btss: remoteproc {
+ compatible = "qcom,ipq5018-btss-pil";
+
+ firmware-name = "qca/bt_fw_patch.mbn";
+
+ clocks = <&gcc GCC_BTSS_LPO_CLK>;
+ clock-names = "btss_lpo_clk";
+ resets = <&gcc GCC_BTSS_BCR>;
+ reset-names = "btss_reset";
+
+ memory-region = <&btss_region>;
+
+ status = "disabled";
+ };
+
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
+ btss_region: bluetooth@7000000 {
+ reg = <0x0 0x07000000 0x0 0x58000>;
+ no-map;
+ };
+
bootloader@4a800000 {
reg = <0x0 0x4a800000 0x0 0x200000>;
no-map;
@@ -647,7 +678,8 @@ watchdog: watchdog@b017000 {
apcs_glb: mailbox@b111000 {
compatible = "qcom,ipq5018-apcs-apps-global",
- "qcom,ipq6018-apcs-apps-global";
+ "qcom,ipq6018-apcs-apps-global",
+ "syscon";
reg = <0x0b111000 0x1000>;
#clock-cells = <1>;
clocks = <&a53pll>, <&xo_board_clk>, <&gcc GPLL0>;
--
2.53.0
^ permalink raw reply related
* [PATCH 4/6] dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Document the Qualcomm IPQ5018 Bluetooth controller.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
.../bindings/net/bluetooth/qcom,ipq5018-bt.yaml | 63 ++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
new file mode 100644
index 000000000000..afd33f851858
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/bluetooth/qcom,ipq5018-bt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm IPQ5018 Bluetooth
+
+maintainers:
+ - George Moussalem <george.moussalem@outlook.com>
+
+properties:
+ compatible:
+ enum:
+ - qcom,ipq5018-bt
+
+ interrupts:
+ items:
+ - description:
+ Interrupt line from the M0 Bluetooth Subsystem to the host processor
+ to notify it of events such as re
+
+ qcom,ipc:
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ items:
+ - items:
+ - description: phandle to a syscon node representing the APCS registers
+ - description: u32 representing offset to the register within the syscon
+ - description: u32 representing the ipc bit within the register
+ description: |
+ These entries specify the outgoing IPC bit used for signaling the remote
+ M0 BTSS core of a host event or for sending an ACK if the remote processor
+ expects it.
+
+ qcom,rproc:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the remote processor node representing the M0 BTSS core.
+
+required:
+ - compatible
+ - interrupts
+ - qcom,ipc
+ - qcom,rproc
+
+allOf:
+ - $ref: bluetooth-controller.yaml#
+ - $ref: qcom,bluetooth-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ bluetooth: bluetooth {
+ compatible = "qcom,ipq5018-bt";
+
+ qcom,ipc = <&apcs_glb 8 23>;
+ interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+ qcom,rproc = <&m0_btss>;
+ };
--
2.53.0
^ permalink raw reply related
* [PATCH 3/6] Bluetooth: btqca: Add IPQ5018 support
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Add the IPQ5018 SoC type and support for loading its firmware.
The firmware tested has been taken from GPL sources of various router
boards. Firmware files needed are:
- qca/bt_fw_patch.mbn
- qca/mpnv10.bin
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
drivers/bluetooth/btqca.c | 16 ++++++++++++++++
drivers/bluetooth/btqca.h | 3 +++
2 files changed, 19 insertions(+)
diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c
index 04ebe290bc78..e136e91976cf 100644
--- a/drivers/bluetooth/btqca.c
+++ b/drivers/bluetooth/btqca.c
@@ -380,6 +380,9 @@ static int qca_tlv_check_data(struct hci_dev *hdev,
break;
case TLV_TYPE_NVM:
+ if (soc_type == QCA_IPQ5018)
+ break;
+
if (fw_size < sizeof(struct tlv_type_hdr))
return -EINVAL;
@@ -794,6 +797,9 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
else
rom_ver = ((soc_ver & 0x00000f00) >> 0x04) | (soc_ver & 0x0000000f);
+ if (soc_type == QCA_IPQ5018)
+ goto download_nvm;
+
if (soc_type == QCA_WCN6750)
qca_send_patch_config_cmd(hdev);
@@ -881,6 +887,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
if (soc_type == QCA_QCA2066 || soc_type == QCA_WCN7850)
qca_read_fw_board_id(hdev, &boardid);
+download_nvm:
/* Download NVM configuration */
config.type = TLV_TYPE_NVM;
if (firmware_name) {
@@ -939,6 +946,10 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
qca_get_nvm_name_by_board(config.fwname, sizeof(config.fwname),
"hmtnv", soc_type, ver, rom_ver, boardid);
break;
+ case QCA_IPQ5018:
+ snprintf(config.fwname, sizeof(config.fwname),
+ "qca/mpnv%02x.bin", rom_ver);
+ break;
default:
snprintf(config.fwname, sizeof(config.fwname),
"qca/nvm_%08x.bin", soc_ver);
@@ -958,6 +969,9 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
return err;
}
+ if (soc_type == QCA_IPQ5018)
+ msleep(NVM_READY_DELAY_MS);
+
switch (soc_type) {
case QCA_QCA2066:
case QCA_QCA6390:
@@ -965,6 +979,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
case QCA_WCN6750:
case QCA_WCN6855:
case QCA_WCN7850:
+ case QCA_IPQ5018:
err = qca_disable_soc_logging(hdev);
if (err < 0)
return err;
@@ -1001,6 +1016,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
case QCA_WCN6750:
case QCA_WCN6855:
case QCA_WCN7850:
+ case QCA_IPQ5018:
/* get fw build info */
err = qca_read_fw_build_info(hdev);
if (err < 0)
diff --git a/drivers/bluetooth/btqca.h b/drivers/bluetooth/btqca.h
index 8f3c1b1c77b3..343cd62d1137 100644
--- a/drivers/bluetooth/btqca.h
+++ b/drivers/bluetooth/btqca.h
@@ -54,6 +54,8 @@
#define QCA_HSP_GF_SOC_ID 0x1200
#define QCA_HSP_GF_SOC_MASK 0x0000ff00
+#define NVM_READY_DELAY_MS 1500
+
enum qca_baudrate {
QCA_BAUDRATE_115200 = 0,
QCA_BAUDRATE_57600,
@@ -158,6 +160,7 @@ enum qca_btsoc_type {
QCA_WCN6750,
QCA_WCN6855,
QCA_WCN7850,
+ QCA_IPQ5018,
};
#if IS_ENABLED(CONFIG_BT_QCA)
--
2.53.0
^ permalink raw reply related
* [PATCH 2/6] remoteproc: qcom: Add M0 BTSS secure PIL driver
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Add support to bring up the M0 core of the bluetooth subsystem found in
the IPQ5018 SoC.
The signed firmware loaded is authenticated by TrustZone. If successful,
the M0 core boots the firmware and the peripheral is taken out of reset
using a Secure Channel Manager call to TrustZone.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
drivers/remoteproc/Kconfig | 12 ++
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++++++++++++++++++++++++++++++
3 files changed, 274 insertions(+)
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index c521c744e7db..6b52f78f1427 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -163,6 +163,18 @@ config PRU_REMOTEPROC
processors on various TI SoCs. It's safe to say N here if you're
not interested in the PRU or if you are unsure.
+config QCOM_M0_BTSS_PIL
+ tristate "Qualcomm M0 BTSS Peripheral Image Loader"
+ depends on OF && ARCH_QCOM
+ select QCOM_MDT_LOADER
+ select QCOM_RPROC_COMMON
+ select QCOM_SCM
+ help
+ Say y here to support the Secure Peripheral Imager Loader for the
+ Qualcomm Bluetooth Subsystem running on the M0 remote processor found
+ in the IPQ5018 SoC. The M0 core is started and stopped using a
+ Secure Channel Manager call to TrustZone.
+
config QCOM_PIL_INFO
tristate
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 1c7598b8475d..df80faf8d0df 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
obj-$(CONFIG_KEYSTONE_REMOTEPROC) += keystone_remoteproc.o
obj-$(CONFIG_MESON_MX_AO_ARC_REMOTEPROC)+= meson_mx_ao_arc.o
obj-$(CONFIG_PRU_REMOTEPROC) += pru_rproc.o
+obj-$(CONFIG_QCOM_M0_BTSS_PIL) += qcom_m0_btss_pil.o
obj-$(CONFIG_QCOM_PIL_INFO) += qcom_pil_info.o
obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o
obj-$(CONFIG_QCOM_Q6V5_COMMON) += qcom_q6v5.o
diff --git a/drivers/remoteproc/qcom_m0_btss_pil.c b/drivers/remoteproc/qcom_m0_btss_pil.c
new file mode 100644
index 000000000000..7168e270e4d4
--- /dev/null
+++ b/drivers/remoteproc/qcom_m0_btss_pil.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/elf.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/soc/qcom/mdt_loader.h>
+
+#include "qcom_common.h"
+
+#define BTSS_PAS_ID 0xc
+
+struct m0_btss {
+ struct device *dev;
+ phys_addr_t mem_phys;
+ phys_addr_t mem_reloc;
+ void __iomem *mem_region;
+ size_t mem_size;
+ struct reset_control *btss_reset;
+};
+
+static int m0_btss_start(struct rproc *rproc)
+{
+ int ret;
+
+ if (!qcom_scm_pas_supported(BTSS_PAS_ID)) {
+ dev_err(rproc->dev.parent,
+ "PAS is not available for peripheral: 0x%x\n",
+ BTSS_PAS_ID);
+ return -ENODEV;
+ }
+
+ ret = qcom_scm_pas_auth_and_reset(BTSS_PAS_ID);
+ if (ret) {
+ dev_err(rproc->dev.parent, "Failed to start rproc: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int m0_btss_stop(struct rproc *rproc)
+{
+ int ret;
+
+ if (rproc->state == RPROC_RUNNING || rproc->state == RPROC_CRASHED) {
+ ret = qcom_scm_pas_shutdown(BTSS_PAS_ID);
+ if (ret) {
+ dev_err(rproc->dev.parent, "Failed to stop rproc: %d\n",
+ ret);
+ return ret;
+ }
+
+ dev_info(rproc->dev.parent, "Successfully stopped rproc\n");
+ }
+
+ return 0;
+}
+
+static int m0_btss_load(struct rproc *rproc, const struct firmware *fw)
+{
+ struct m0_btss *desc = rproc->priv;
+ const struct elf32_phdr *phdrs;
+ const struct firmware *seg_fw;
+ const struct elf32_phdr *phdr;
+ const struct elf32_hdr *ehdr;
+ void __iomem *metadata;
+ size_t metadata_size;
+ int i, ret;
+
+ ehdr = (const struct elf32_hdr *)fw->data;
+ phdrs = (const struct elf32_phdr *)(ehdr + 1);
+
+ ret = request_firmware(&fw, rproc->firmware, rproc->dev.parent);
+ if (ret) {
+ dev_err(rproc->dev.parent, "Failed to request firmware: %d\n",
+ ret);
+ return ret;
+ }
+
+ metadata = qcom_mdt_read_metadata(fw, &metadata_size, rproc->firmware,
+ rproc->dev.parent);
+ if (IS_ERR(metadata)) {
+ ret = PTR_ERR(metadata);
+ dev_err(rproc->dev.parent,
+ "Failed to read firmware metadata: %d\n", ret);
+ goto release_fw;
+ }
+
+ ret = qcom_scm_pas_init_image(BTSS_PAS_ID, metadata,
+ metadata_size, NULL);
+ if (ret) {
+ dev_err(rproc->dev.parent, "PAS init image failed: %d\n", ret);
+ goto free_metadata;
+ }
+
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ char *seg_name __free(kfree) = kstrdup(rproc->firmware,
+ GFP_KERNEL);
+ if (!seg_name)
+ return -ENOMEM;
+
+ phdr = &phdrs[i];
+
+ /* Only process valid loadable data segments */
+ if (phdr->p_type != PT_LOAD || !phdr->p_memsz)
+ continue;
+
+ if (phdr->p_vaddr + phdr->p_filesz > desc->mem_size) {
+ dev_err(rproc->dev.parent,
+ "Segment data exceeds the reserved memory area!\n");
+ goto free_metadata;
+ }
+
+ /* Check if firmware is split across multiple segment files */
+ if (phdr->p_offset > fw->size ||
+ phdr->p_offset + phdr->p_filesz > fw->size) {
+ sprintf(seg_name + strlen(seg_name) - 3, "b%02d", i);
+ ret = request_firmware(&seg_fw, seg_name,
+ rproc->dev.parent);
+ if (ret) {
+ dev_err(rproc->dev.parent,
+ "Could not find split segment binary: %s\n",
+ seg_name);
+ goto free_metadata;
+ }
+
+ /*
+ * Use the virtual instead of the physical address as
+ * the offset
+ */
+ memcpy_toio(desc->mem_region + phdr->p_vaddr,
+ seg_fw->data, phdr->p_filesz);
+
+ release_firmware(seg_fw);
+ } else {
+ memcpy_toio(desc->mem_region + phdr->p_vaddr,
+ fw->data + phdr->p_offset, phdr->p_filesz);
+ }
+ }
+
+ return 0;
+
+free_metadata:
+ kfree(metadata);
+release_fw:
+ release_firmware(fw);
+ return ret;
+}
+
+static const struct rproc_ops m0_btss_ops = {
+ .start = m0_btss_start,
+ .stop = m0_btss_stop,
+ .load = m0_btss_load,
+ .get_boot_addr = rproc_elf_get_boot_addr,
+};
+
+static int m0_btss_alloc_memory_region(struct m0_btss *desc)
+{
+ struct device *dev = desc->dev;
+ struct resource res;
+ int ret;
+
+ ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res);
+ if (ret) {
+ dev_err(dev, "unable to acquire memory-region resource\n");
+ return ret;
+ }
+
+ desc->mem_phys = res.start;
+ desc->mem_reloc = res.start;
+ desc->mem_size = resource_size(&res);
+ desc->mem_region = devm_ioremap(dev, desc->mem_phys, desc->mem_size);
+ if (!desc->mem_region) {
+ dev_err(dev, "unable to map memory region: %pR\n", &res);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int m0_btss_pil_probe(struct platform_device *pdev)
+{
+ // struct reset_control *btss_reset;
+ struct device *dev = &pdev->dev;
+ const char *fw_name = NULL;
+ struct m0_btss *desc;
+ struct clk *lpo_clk;
+ struct rproc *rproc;
+ int ret;
+
+ ret = of_property_read_string(dev->of_node, "firmware-name",
+ &fw_name);
+ if (ret < 0)
+ return ret;
+
+ rproc = devm_rproc_alloc(dev, "m0btss", &m0_btss_ops,
+ fw_name, sizeof(*desc));
+ if (!rproc) {
+ dev_err(dev, "failed to allocate rproc\n");
+ return -ENOMEM;
+ }
+
+ desc = rproc->priv;
+ desc->dev = dev;
+
+ ret = m0_btss_alloc_memory_region(desc);
+ if (ret)
+ return ret;
+
+ lpo_clk = devm_clk_get_enabled(dev, "btss_lpo_clk");
+ if (IS_ERR(lpo_clk))
+ return dev_err_probe(dev, PTR_ERR(lpo_clk),
+ "Failed to get lpo clock\n");
+
+ desc->btss_reset = devm_reset_control_get(dev, "btss_reset");
+ if (IS_ERR_OR_NULL(desc->btss_reset))
+ return dev_err_probe(dev, PTR_ERR(desc->btss_reset),
+ "unable to acquire btss_reset\n");
+
+ ret = reset_control_deassert(desc->btss_reset);
+ if (ret)
+ return dev_err_probe(rproc->dev.parent, ret,
+ "Failed to deassert reset\n");
+
+ rproc->auto_boot = false;
+ ret = devm_rproc_add(dev, rproc);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, rproc);
+
+ return 0;
+}
+
+static const struct of_device_id m0_btss_of_match[] = {
+ { .compatible = "qcom,ipq5018-btss-pil" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, m0_btss_of_match);
+
+static struct platform_driver m0_btss_pil_driver = {
+ .probe = m0_btss_pil_probe,
+ .driver = {
+ .name = "qcom-m0-btss-pil",
+ .of_match_table = m0_btss_of_match,
+ },
+};
+
+module_platform_driver(m0_btss_pil_driver);
+
+MODULE_DESCRIPTION("Qualcomm M0 Bluetooth Subsystem Peripheral Image Loader");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH 1/6] dt-bindings: remoteproc: document M0 Bluetooth Subsystem secure PIL
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Document the M0 Bluetooth Subsystem remote processor core found in the
Qualcomm IPQ5018 SoC. Firmware loaded is authenticated via TrustZone.
The firmware running on the M0 core provides bluetooth functionality.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
.../bindings/remoteproc/qcom,m0-btss-pil.yaml | 72 ++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,m0-btss-pil.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,m0-btss-pil.yaml
new file mode 100644
index 000000000000..397bb6815d71
--- /dev/null
+++ b/Documentation/devicetree/bindings/remoteproc/qcom,m0-btss-pil.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/remoteproc/qcom,m0-btss-pil.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm M0 BTSS Peripheral Image Loader
+
+maintainers:
+ - George Moussalem <george.moussalem@outlook.com>
+
+description:
+ Qualcomm M0 BTSS Peripheral Secure Image Loader loads firmware and powers up
+ the M0 BTSS remote processor core on the Qualcomm IPQ5018 SoC.
+
+properties:
+ compatible:
+ enum:
+ - qcom,ipq5018-btss-pil
+
+ firmware-name:
+ maxItems: 1
+ description: Firmware name for the M0 Bluetooth Subsystem core
+
+ clocks:
+ items:
+ - description: M0 BTSS low power oscillator clock
+
+ clock-names:
+ items:
+ - const: btss_lpo_clk
+
+ memory-region:
+ items:
+ - description: M0 BTSS reserved memory carveout
+
+ resets:
+ items:
+ - description: M0 BTSS reset
+
+ reset-names:
+ items:
+ - const: btss_reset
+
+required:
+ - compatible
+ - firmware-name
+ - clocks
+ - clock-names
+ - resets
+ - reset-names
+ - memory-region
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,gcc-ipq5018.h>
+ #include <dt-bindings/reset/qcom,gcc-ipq5018.h>
+
+ remoteproc {
+ compatible = "qcom,ipq5018-btss-pil";
+
+ firmware-name = "qca/bt_fw_patch.mbn";
+
+ clocks = <&gcc GCC_BTSS_LPO_CLK>;
+ clock-names = "btss_lpo_clk";
+ resets = <&gcc GCC_BTSS_BCR>;
+ reset-names = "btss_reset";
+
+ memory-region = <&btss_region>;
+ };
--
2.53.0
^ permalink raw reply related
* [PATCH 0/6] Add support for IPQ5018 Bluetooth
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
Hello,
This patch series introduces Bluetooth support for IPQ5018.
Bluetooth firmware on the IPQ5018 platform runs on the M0 co-processor.
The firmware is loaded by the host into a dedicated reserved memory
carveout and authenticated by TrustZone. A Secure Channel Manager (SCM)
call safely brings the peripheral core out of reset.
A shared memory ring buffer topology handles runtime data frame
transport between the host APSS and the M0 core. An outgoing APCS IPC
bit and an incoming GIC interrupt handle host/guest signaling.
This series has been tested and verified on various IPQ5018 router
boards utilizing firmware extracted from GPL distributions, using both
mdt and mbn file formats.
[ 14.781511] Bluetooth: hci0: QCA Product ID :0x00000016
[ 14.781583] Bluetooth: hci0: QCA SOC Version :0x20180100
[ 14.785926] Bluetooth: hci0: QCA ROM Version :0x00000100
[ 14.791546] Bluetooth: hci0: QCA Patch Version:0x00003ded
[ 14.796698] Bluetooth: hci0: QCA controller version 0x01000100
[ 14.802217] Bluetooth: hci0: QCA Downloading qca/mpnv10.bin
[ 16.393850] Bluetooth: hci0: QCA Build Info: BTFW.MAPLE.1.0.0-00102-MPL_ROM_PATCHZ-1
Best regards,
George Moussalem
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
George Moussalem (6):
dt-bindings: remoteproc: document M0 Bluetooth Subsystem secure PIL
remoteproc: qcom: Add M0 BTSS secure PIL driver
Bluetooth: btqca: Add IPQ5018 support
dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
.../bindings/net/bluetooth/qcom,ipq5018-bt.yaml | 63 ++
.../bindings/remoteproc/qcom,m0-btss-pil.yaml | 72 ++
arch/arm64/boot/dts/qcom/ipq5018.dtsi | 34 +-
drivers/bluetooth/Kconfig | 11 +
drivers/bluetooth/Makefile | 1 +
drivers/bluetooth/btqca.c | 16 +
drivers/bluetooth/btqca.h | 3 +
drivers/bluetooth/btqcomipc.c | 939 +++++++++++++++++++++
drivers/remoteproc/Kconfig | 12 +
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++
11 files changed, 1412 insertions(+), 1 deletion(-)
---
base-commit: 4d1ab324fcb7d20df5a071edb0304461846fdc12
change-id: 20260625-ipq5018-bluetooth-06ff66c9d753
prerequisite-message-id: <20260612-block-as-nvmem-v5-0-95e0b30fff90@oss.qualcomm.com>
prerequisite-patch-id: 6ce8686c1683f468d86b4502f5ec9d19c392a382
prerequisite-patch-id: e362f7fcbacff716b7ef720e6780786a7d88c013
prerequisite-patch-id: 9168930e40551e842c8171d5433a6f39ad4b78a4
prerequisite-patch-id: 64fecfbd1e085d7d2ab0ae23295ca34ec8e14c5e
prerequisite-patch-id: 566804aaa690ee9aa285d0fd75fd16d94fbadebf
prerequisite-patch-id: dc18bec338f54b3051f4523f9d1d3c0566a20ccd
prerequisite-patch-id: b6b3eb46429936ab49423d295433daf47981db0f
prerequisite-patch-id: 75caa99e3bbcdf41b6462b9f5f703bea1d4a65fa
prerequisite-patch-id: 35e9968f482f78ca233eb0306d9c5fdbff093175
Best regards,
--
George Moussalem <george.moussalem@outlook.com>
^ permalink raw reply
* Re: [PATCH V2 1/8] PCI: imx6: Add skip_pwrctrl_off flag support
From: Manivannan Sadhasivam @ 2026-06-25 13:57 UTC (permalink / raw)
To: Sherry Sun
Cc: Frank Li (OSS), Sherry Sun (OSS), robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, Frank Li,
s.hauer@pengutronix.de, kernel@pengutronix.de, festevam@gmail.com,
Amitkumar Karwar, Neeraj Sanjay Kale, marcel@holtmann.org,
luiz.dentz@gmail.com, Hongxing Zhu, l.stach@pengutronix.de,
lpieralisi@kernel.org, kwilczynski@kernel.org,
bhelgaas@google.com, brgl@kernel.org, imx@lists.linux.dev,
linux-pci@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-bluetooth@vger.kernel.org, linux-pm@vger.kernel.org
In-Reply-To: <VI0PR04MB12114BCBD405505888063284492EC2@VI0PR04MB12114.eurprd04.prod.outlook.com>
On Thu, Jun 25, 2026 at 07:25:46AM +0000, Sherry Sun wrote:
> > Subject: Re: [PATCH V2 1/8] PCI: imx6: Add skip_pwrctrl_off flag support
> >
> > On Wed, Jun 24, 2026 at 07:09:26AM +0000, Sherry Sun wrote:
> > > > Subject: Re: [PATCH V2 1/8] PCI: imx6: Add skip_pwrctrl_off flag
> > > > support
> > > >
> > > > On Tue, Jun 23, 2026 at 11:07:28AM +0800, Sherry Sun (OSS) wrote:
> > > > > From: Sherry Sun <sherry.sun@nxp.com>
> > > > >
> > > > > Use dw_pcie_rp::skip_pwrctrl_off to avoid powering off devices
> > > > > during suspend to preserve wakeup capability of the devices and
> > > > > also not to power on the devices in the init path.
> > > > > This allows controller power-off to be skipped when some devices(e.g.
> > > > > M.2 cards key E without auxiliary power) required to support PCIe
> > > > > L2 link state and wake-up mechanisms.
> > > > >
> > > > > Signed-off-by: Sherry Sun <sherry.sun@nxp.com>
> > > > > ---
> > > > > drivers/pci/controller/dwc/pci-imx6.c | 36
> > > > > +++++++++++++++++----------
> > > > > 1 file changed, 23 insertions(+), 13 deletions(-)
> > > > >
> > > > > diff --git a/drivers/pci/controller/dwc/pci-imx6.c
> > > > > b/drivers/pci/controller/dwc/pci-imx6.c
> > > > > index 0fa716d1ed75..ff5a9565dbbf 100644
> > > > > --- a/drivers/pci/controller/dwc/pci-imx6.c
> > > > > +++ b/drivers/pci/controller/dwc/pci-imx6.c
> > > > > @@ -1382,16 +1382,20 @@ static int imx_pcie_host_init(struct
> > > > > dw_pcie_rp
> > > > *pp)
> > > > > }
> > > > > }
> > > > >
> > > > > - ret = pci_pwrctrl_create_devices(dev);
> > > > > - if (ret) {
> > > > > - dev_err(dev, "failed to create pwrctrl devices\n");
> > > > > - goto err_reg_disable;
> > > > > + if (!pci->suspended) {
> > > > > + ret = pci_pwrctrl_create_devices(dev);
> > > >
> > > > Is possible move pci_pwrctrl_create_devices() of
> > > > pci_pwrctrl_create_devices
> > > >
> > > > and call it direct at probe() function, like other regulator_get function.
> > > >
> > >
> > > Hi Frank,
> > > That makes sense. However, if we move pci_pwrctrl_create_devices () to
> > > probe(), we may need to add the following goto err_pwrctrl_destroy
> > > path in imx_pcie_probe() to properly handle errors from
> > > pci_pwrctrl_power_on_devices(), is that acceptable?
> >
> > Can you add a API devm_pci_pwrctrl_create_devices() ?
> >
>
> Hi Frank, we cannot unconditionally destroy the pwrctrl devices
> when probing fails by using devm API.
> Since we need to check the return value of
> pci_pwrctrl_power_on_devices() for example EPROBE_DEFER to decide
> whether to destroy the pwrctrl devices to avoid the deferred probe loop.
>
> You can find more related discussion here.
> https://lore.kernel.org/all/tutxwjciedqoje5wxvtin4h637auni5zzpvb7rtfg4uticxoux@yfl6xg7oht7t/
>
Yes. Ideally we should try to do a blocking wait in the pwrctrl core until all
the pwrctrl drivers are probed, instead of deferring probe. Hopefully, I'll get
to it soon.
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply
* RE: [v2] Bluetooth: btusb: Add USB ID 13d3:3625 for MediaTek MT7922
From: bluez.test.bot @ 2026-06-25 10:16 UTC (permalink / raw)
To: linux-bluetooth, monesss315
In-Reply-To: <20260625083230.300651-1-monesss315@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1449 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1116370
---Test result---
Test Summary:
CheckPatch PASS 1.08 seconds
VerifyFixes PASS 0.27 seconds
VerifySignedoff PASS 0.21 seconds
GitLint FAIL 0.52 seconds
SubjectPrefix PASS 0.24 seconds
BuildKernel PASS 27.04 seconds
CheckAllWarning PASS 29.69 seconds
CheckSparse PASS 29.46 seconds
BuildKernel32 PASS 26.30 seconds
CheckKernelLLVM SKIP 0.00 seconds
TestRunnerSetup PASS 500.16 seconds
IncrementalBuild PASS 25.76 seconds
Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[v2] Bluetooth: btusb: Add USB ID 13d3:3625 for MediaTek MT7922
63: B1 Line exceeds max length (82>80): " - Add /sys/kernel/debug/usb/devices output to the commit message (Paul Menzel)"
##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found
https://github.com/bluez/bluetooth-next/pull/351
---
Regards,
Linux Bluetooth
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox