Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH v1] Bluetooth: btintel: Add UEFI BRDS and BPMO for PeP
@ 2026-10-30 11:50 Catherine L
  0 siblings, 0 replies; only message in thread
From: Catherine L @ 2026-10-30 11:50 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: kiran.k, chethan.tumkur.narayan, Catherine L

DCR-3475 adds per-modulation SAR offsets for PeP platforms. PeP
platforms expose BRDS and BPMO data through UEFI variables instead of
the legacy ACPI BRDS path.

Add UEFI-based SAR initialization for supported CNVi/CNVr combinations:
- SCP2, SCP2_C0, or SCP2F CNVi with PeP CNVr 0xA30
- Exclude WHP2 CNVr 0xA10, even when UEFI variables are present
- Read UefiCnvBtBRDS and write SarBrdsLimit2G4 DDC 0x0311
- Read UefiCnvBtBPMO and write BPMO DDC 0x0359, 0x035A, and 0x035B
- Preserve fixed UEFI structures, including header and mode fields
- Handle unsupported and oversized future UEFI revisions gracefully
- Send HCI_Intel_SarInitializeState_CMD (0xFE25) after SAR DDC writes

BRDS and BPMO are independent. Missing or disabled BIOS data is
non-fatal, and firmware proceeds with default SAR values. The existing
ACPI BRDS/SAR path, including mutual SAR and its 0xFE25 completion
command, remains unchanged. No ACPI BPMO path is added.

Initial UEFI validation passed 6/6 scenarios on a PEP STC card using
FW 255-255.255. Compatibility validation confirmed that platforms
without UEFI variables remain operational.

[RELEASEINFO]
program=all
ticket=jira:BT-479365
component=ibtpci
hw=all
sdl=na
test=notrelevant
justification=valid: UEFI BRDS/BPMO handling implemented for PeP CNVr 0xA30

Signed-off-by: Catherine L  <catherine.l@intel.com>
---
 drivers/bluetooth/btintel.c | 266 ++++++++++++++++++++++++++++++++++--
 drivers/bluetooth/btintel.h |  29 ++++
 2 files changed, 284 insertions(+), 11 deletions(-)

diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c
index b9bb6980bada..017681c7e3ce 100644
--- a/drivers/bluetooth/btintel.c
+++ b/drivers/bluetooth/btintel.c
@@ -28,8 +28,14 @@
 #define ECDSA_OFFSET		644
 #define ECDSA_HEADER_LEN	320
 
+#define BTINTEL_EFI_BPMO	L"UefiCnvBtBPMO"
+#define BTINTEL_EFI_BRDS	L"UefiCnvBtBRDS"
 #define BTINTEL_EFI_DSBR	L"UefiCnvCommonDSBR"
 
+static efi_guid_t btintel_efi_sar_guid =
+	EFI_GUID(0x42780dd5, 0x9a7d, 0x404c,
+		 0x80, 0xe4, 0x7f, 0x70, 0x94, 0x36, 0x03, 0x94);
+
 enum {
 	DSM_SET_WDISABLE2_DELAY = 1,
 	DSM_SET_RESET_METHOD = 3,
@@ -3092,6 +3098,7 @@ static union acpi_object *btintel_acpi_get_bt_pkg(union acpi_object *buffer)
 	}
 	return ERR_PTR(-ENOENT);
 }
+#endif /* CONFIG_ACPI */
 
 static int btintel_send_sar_ddc(struct hci_dev *hdev, struct btintel_cp_ddc_write *data, u8 len)
 {
@@ -3107,6 +3114,7 @@ static int btintel_send_sar_ddc(struct hci_dev *hdev, struct btintel_cp_ddc_writ
 	return 0;
 }
 
+#ifdef CONFIG_ACPI
 static int btintel_send_edr(struct hci_dev *hdev, struct btintel_cp_ddc_write *cmd,
 			    int id, struct btintel_sar_inc_pwr *sar)
 {
@@ -3162,7 +3170,153 @@ static int btintel_send_edr3(struct hci_dev *hdev, struct btintel_cp_ddc_write *
 	cmd->data[0] = sar->edr3;
 	return btintel_send_sar_ddc(hdev, cmd, 4);
 }
+#endif /* CONFIG_ACPI */
+
+/* btintel_send_sar_complete - send HCI_Intel_SarInitializeState_CMD (0xFE25)
+ *
+ * Sent once after all SAR DDC writes are complete.
+ */
+static int btintel_send_sar_complete(struct hci_dev *hdev)
+{
+	struct sk_buff *skb;
+	bool enable = true;
+
+	skb = __hci_cmd_sync(hdev, 0xfe25, 1, &enable, HCI_CMD_TIMEOUT);
+	if (IS_ERR(skb)) {
+		bt_dev_err(hdev, "BT_SAR: failed to send SAR initialization complete (%ld)",
+			   PTR_ERR(skb));
+		return PTR_ERR(skb);
+	}
+	kfree_skb(skb);
+	return 0;
+}
+
+/* btintel_write_bpmo_ddc - write BPMO DDC constants to firmware
+ *
+ * Writes DDC_ID_SAR_BPMO_ENABLE (1 byte), DDC_ID_SAR_BPMO_OFFSET_A (16 bytes)
+ * and DDC_ID_SAR_BPMO_OFFSET_B (16 bytes) via HCI WriteDdcConst (0xfc8b).
+ * Values are passed through unchanged from the BIOS UEFI variable; no
+ * arithmetic is performed by the driver (HLD DCR-3475 §5.1 step 2).
+ */
+static int btintel_write_bpmo_ddc(struct hci_dev *hdev,
+				  const struct btintel_bpmo *bpmo)
+{
+	u8 buffer[sizeof(struct btintel_cp_ddc_write) + BTINTEL_BPMO_NUM_MODULATIONS];
+	struct btintel_cp_ddc_write *cmd = (void *)buffer;
+	int ret;
+
+	/* Enable flag (1 byte) — SarBpmoEnable (0x0359) */
+	cmd->len     = 3;
+	cmd->id      = cpu_to_le16(BTINTEL_DDC_ID_SAR_BPMO_ENABLE);
+	cmd->data[0] = !!(le32_to_cpu(bpmo->mode) & BIT(0));
+	ret = btintel_send_sar_ddc(hdev, cmd, 4);
+	if (ret) {
+		bt_dev_err(hdev, "BT_BPMO: failed to write DDC enable (0x%04x): %d",
+			   BTINTEL_DDC_ID_SAR_BPMO_ENABLE, ret);
+		return ret;
+	}
+	/* Chain A per-modulation offsets (16 bytes) — SarBpmoChainA (0x035A) */
+	cmd->len = 2 + BTINTEL_BPMO_NUM_MODULATIONS;
+	cmd->id  = cpu_to_le16(BTINTEL_DDC_ID_SAR_BPMO_CHAIN_A);
+	memcpy(cmd->data, bpmo->chain_a, BTINTEL_BPMO_NUM_MODULATIONS);
+	ret = btintel_send_sar_ddc(hdev, cmd,
+				   sizeof(*cmd) + BTINTEL_BPMO_NUM_MODULATIONS);
+	if (ret) {
+		bt_dev_err(hdev, "BT_BPMO: failed to write DDC chain A (0x%04x): %d",
+			   BTINTEL_DDC_ID_SAR_BPMO_CHAIN_A, ret);
+		return ret;
+	}
+	/* Chain B per-modulation offsets (16 bytes) — SarBpmoChainB (0x035B) */
+	cmd->len = 2 + BTINTEL_BPMO_NUM_MODULATIONS;
+	cmd->id  = cpu_to_le16(BTINTEL_DDC_ID_SAR_BPMO_CHAIN_B);
+	memcpy(cmd->data, bpmo->chain_b, BTINTEL_BPMO_NUM_MODULATIONS);
+	ret = btintel_send_sar_ddc(hdev, cmd,
+				   sizeof(*cmd) + BTINTEL_BPMO_NUM_MODULATIONS);
+	if (ret) {
+		bt_dev_err(hdev, "BT_BPMO: failed to write DDC chain B (0x%04x): %d",
+			   BTINTEL_DDC_ID_SAR_BPMO_CHAIN_B, ret);
+		return ret;
+	}
+	return 0;
+}
+
+/* btintel_uefi_get_bpmo - read BT per-modulation SAR offsets from UEFI variable
+ *
+ * Reads UefiCnvBtBPMO (CNV_UEFI_VAR_BPMO, Revision 0) from the UEFI runtime
+ * variable store for the PeP/SCP2 path.
+ *
+ * Returns  0           on success; @bpmo is filled.
+ * Returns -ENOENT      if the variable is absent.
+ * Returns -EOPNOTSUPP  if EFI runtime is unavailable or revision unsupported.
+ * Returns -EINVAL/-EIO on structural or read errors.
+ *
+ * Layout confirmed by BIOS team (Kobi), total 37 bytes:
+ *   u8  header/revision  (offset 0)
+ *   u32 mode      (offset 1, no padding — struct is __packed)
+ *   u8  chain_a[16] (offset 5)
+ *   u8  chain_b[16] (offset 21)
+ */
+static int btintel_uefi_get_bpmo(struct hci_dev *hdev, struct btintel_bpmo *bpmo)
+{
+	unsigned long data_size = sizeof(*bpmo);
+	efi_status_t status;
+
+	if (!IS_ENABLED(CONFIG_EFI))
+		return -EOPNOTSUPP;
 
+	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
+		return -EOPNOTSUPP;
+
+	status = efi.get_variable(BTINTEL_EFI_BPMO, &btintel_efi_sar_guid, NULL,
+				  &data_size, bpmo);
+	if (status == EFI_NOT_FOUND)
+		return -ENOENT;
+	/* larger variable = future revision; treat as unsupported rather than error */
+	if (status == EFI_BUFFER_TOO_SMALL)
+		return -EOPNOTSUPP;
+	if (status != EFI_SUCCESS) {
+		bt_dev_warn(hdev, "BT_BPMO: UEFI variable read error (0x%lx)",
+			    status);
+		return -EIO;
+	}
+
+	if (data_size < sizeof(*bpmo)) {
+		bt_dev_warn(hdev, "BT_BPMO: UEFI variable too small (%lu < %zu)",
+			    data_size, sizeof(*bpmo));
+		return -EINVAL;
+	}
+
+	if (bpmo->header != 0) {
+		bt_dev_dbg(hdev, "BT_BPMO: unsupported UEFI revision %u",
+			   bpmo->header);
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+/* btintel_set_bpmo - apply UEFI BT per-modulation SAR offsets to firmware */
+static int btintel_set_bpmo(struct hci_dev *hdev)
+{
+	struct btintel_bpmo bpmo;
+	int ret;
+
+	ret = btintel_uefi_get_bpmo(hdev, &bpmo);
+	if (ret == 0) {
+		if (!(le32_to_cpu(bpmo.mode) & BIT(0)))
+			return 0;
+		ret = btintel_write_bpmo_ddc(hdev, &bpmo);
+		if (ret)
+			bt_dev_err(hdev, "BT_BPMO: DDC write failed (%d)", ret);
+		return ret;
+	}
+
+	if (ret != -ENOENT && ret != -EOPNOTSUPP)
+		bt_dev_warn(hdev, "BT_BPMO: UEFI read failed (%d)", ret);
+	return -ENOENT;
+}
+
+#ifdef CONFIG_ACPI
 static int btintel_set_legacy_sar(struct hci_dev *hdev, struct btintel_sar_inc_pwr *sar)
 {
 	struct btintel_cp_ddc_write *cmd;
@@ -3202,9 +3356,7 @@ static int btintel_set_legacy_sar(struct hci_dev *hdev, struct btintel_sar_inc_p
 static int btintel_set_mutual_sar(struct hci_dev *hdev, struct btintel_sar_inc_pwr *sar)
 {
 	struct btintel_cp_ddc_write *cmd;
-	struct sk_buff *skb;
 	u8 buffer[64];
-	bool enable;
 	int ret;
 
 	cmd = (void *)buffer;
@@ -3249,15 +3401,7 @@ static int btintel_set_mutual_sar(struct hci_dev *hdev, struct btintel_sar_inc_p
 	if (ret)
 		return ret;
 
-	enable = true;
-	skb = __hci_cmd_sync(hdev, 0xfe25, 1, &enable, HCI_CMD_TIMEOUT);
-	if (IS_ERR(skb)) {
-		bt_dev_warn(hdev, "Failed to send Intel SAR Enable (%ld)", PTR_ERR(skb));
-		return PTR_ERR(skb);
-	}
-
-	kfree_skb(skb);
-	return 0;
+	return btintel_send_sar_complete(hdev);
 }
 
 /* btintel_send_sar_rev2_band - send DDC command for one Rev2 sub-band
@@ -3544,9 +3688,109 @@ static int btintel_acpi_set_sar(struct hci_dev *hdev, struct intel_version_tlv *
 }
 #endif /* CONFIG_ACPI */
 
+/* btintel_uefi_get_brds - read SAR limits from UEFI variable UefiCnvBtBRDS
+ *
+ * Primary SAR data source that expose BRDS
+ * via UEFI instead of ACPI.  Uses the same GUID as UefiCnvBtBPMO.
+ *
+ * Writes BRDS DDC only — does NOT call btintel_set_bpmo() or send 0xFE25.
+ * BPMO and 0xFE25 are handled independently by the caller.
+ *
+ * CNV_UEFI_VAR_BRDS revision 2 layout (confirmed by BIOS team, 19 bytes):
+ *   u8  header          - CNV_UEFI_VAR_HEADER (0x02 = revision 2)
+ *   u32 mode            - bit 0: SAR enabled (0=disabled, 1=enabled)
+ *   u32 inc_power_mode  - 0=enabled, 1=disabled
+ *   u8  chain_a[5]      - BT_SAR_POWER_LIMITS chain A [2G4,5G2,5G8,6G1,6G3]
+ *   u8  chain_b[5]      - BT_SAR_POWER_LIMITS chain B [2G4,5G2,5G8,6G1,6G3]
+ */
+static int btintel_uefi_get_brds(struct hci_dev *hdev)
+{
+	struct {
+		u8  header;      /* CNV_UEFI_VAR_HEADER: 0x02 = revision 2 */
+		__le32 mode;     /* bit 0: 1=SAR enabled, 0=disabled        */
+		__le32 inc_power_mode; /* 0=enabled, 1=disabled            */
+		u8  chain_a[5];  /* sub-band limits chain A: [2G4,5G2,5G8,6G1,6G3] */
+		u8  chain_b[5];  /* sub-band limits chain B: [2G4,5G2,5G8,6G1,6G3] */
+	} __packed var;
+	unsigned long data_size = sizeof(var);
+	efi_status_t status;
+
+	if (!IS_ENABLED(CONFIG_EFI))
+		return -EOPNOTSUPP;
+
+	if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
+		return -EOPNOTSUPP;
+
+	status = efi.get_variable(BTINTEL_EFI_BRDS, &btintel_efi_sar_guid, NULL,
+				  &data_size, &var);
+	if (status == EFI_NOT_FOUND)
+		return -ENOENT;
+	/* larger variable = future revision; treat as unsupported rather than error */
+	if (status == EFI_BUFFER_TOO_SMALL)
+		return -EOPNOTSUPP;
+	if (status != EFI_SUCCESS) {
+		bt_dev_warn(hdev, "BT_SAR: UEFI BRDS read error (0x%lx)", status);
+		return -EIO;
+	}
+
+	if (data_size < sizeof(var)) {
+		bt_dev_warn(hdev, "BT_SAR: UEFI BRDS too small (%lu < %zu)",
+			    data_size, sizeof(var));
+		return -EINVAL;
+	}
+
+	if (var.header != 0x02) {
+		bt_dev_warn(hdev, "BT_SAR: UEFI BRDS unsupported revision 0x%02x", var.header);
+		return -EOPNOTSUPP;
+	}
+
+	if (!(le32_to_cpu(var.mode) & BIT(0))) {
+		bt_dev_dbg(hdev, "BT_SAR: UEFI BRDS SAR not enabled in BIOS");
+		return -EOPNOTSUPP;
+	}
+
+	/* BT operates at 2.4 GHz only — write SarBrdsLimit2G4 (0x0311) with chain A/B pair. */
+	{
+		u8 buf[sizeof(struct btintel_cp_ddc_write) + 2];
+		struct btintel_cp_ddc_write *cmd = (void *)buf;
+		int ret;
+
+		cmd->len     = 4; /* 2 (id) + 2 (data) */
+		cmd->id      = cpu_to_le16(BTINTEL_DDC_ID_SAR_BRDS_LIMIT_2G4);
+		cmd->data[0] = var.chain_a[0]; /* subBand2G4 Chain A */
+		cmd->data[1] = var.chain_b[0]; /* subBand2G4 Chain B */
+
+		ret = btintel_send_sar_ddc(hdev, cmd, sizeof(*cmd) + 2);
+		if (ret) {
+			bt_dev_warn(hdev, "BT_SAR: BRDS DDC write failed (0x%04x): %d",
+				    BTINTEL_DDC_ID_SAR_BRDS_LIMIT_2G4, ret);
+			return ret;
+		}
+	}
+	return 0;
+}
+
 static int btintel_set_specific_absorption_rate(struct hci_dev *hdev,
 						struct intel_version_tlv *ver)
 {
+	u16 cnvi = ver->cnvi_top & 0xfff;
+	u16 cnvr = ver->cnvr_top & 0xfff;
+	int ret;
+
+	/* SCP2/SCP2F with PeP CNVr: UEFI path, independent of CONFIG_ACPI */
+	if ((cnvi == BTINTEL_CNVI_SCP2 || cnvi == BTINTEL_CNVI_SCP2_C0 ||
+	     cnvi == BTINTEL_CNVI_SCP2F) && cnvr == BTINTEL_CNVR_PEP) {
+		ret = btintel_uefi_get_brds(hdev);
+		if (ret && ret != -ENOENT && ret != -EOPNOTSUPP)
+			bt_dev_warn(hdev, "BT_SAR: BRDS DDC failed (%d)", ret);
+
+		ret = btintel_set_bpmo(hdev);
+		if (ret && ret != -ENOENT)
+			bt_dev_warn(hdev, "BT_BPMO: failed (%d)", ret);
+
+		return btintel_send_sar_complete(hdev);
+	}
+
 #ifdef CONFIG_ACPI
 	return btintel_acpi_set_sar(hdev, ver);
 #endif
diff --git a/drivers/bluetooth/btintel.h b/drivers/bluetooth/btintel.h
index d5e125ec5168..338a4beed5cc 100644
--- a/drivers/bluetooth/btintel.h
+++ b/drivers/bluetooth/btintel.h
@@ -71,6 +71,7 @@ struct intel_tlv {
 /* CNVR */
 #define BTINTEL_CNVR_FMP2		0x910
 #define BTINTEL_CNVR_WHP2		0xA10	/* Whale Peak2 - Panther Lake */
+#define BTINTEL_CNVR_PEP		0xA30   /* PeP2 / PeP STC */
 
 #define BTINTEL_IMG_BOOTLOADER		0x01	/* Bootloader image */
 #define BTINTEL_IMG_IML			0x02	/* Intermediate image */
@@ -222,6 +223,34 @@ struct btintel_sar_inc_pwr {
 	u8	le_lr;
 };
 
+/* DCR-3475: BT SAR Per Modulation Offset (BPMO)
+ *
+ * BPMO_NUM_MODULATIONS = 16 (indices 0-12 active, 13-15 reserved).
+ */
+#define BTINTEL_BPMO_NUM_MODULATIONS	16
+
+/* BPMO UEFI variable (Revision 0) layout:
+ *   [0]       Header/revision (0 = Revision 0)
+ *   [1..4]    Mode (0 = disabled, 1 = enabled, little-endian u32)
+ *   [5..20]   Chain A per-modulation offsets (u8, format xxxxx.yyy)
+ *   [21..36]  Chain B per-modulation offsets (u8, format xxxxx.yyy)
+ * Index-to-modulation mapping (sc_sar.h scSar_bpmoModulation_en):
+ *   0=BR, 1=EDR2M, 2=EDR3M, 3=LE1M, 4=LE2M, 5=CS, 6=LRS2, 7=LRS8,
+ *   8=HDT2, 9=HDT3, 10=HDT4, 11=HDT6, 12=HDT7P5, 13-15=reserved
+ */
+struct btintel_bpmo {
+	u8	header;
+	__le32	mode;
+	u8	chain_a[BTINTEL_BPMO_NUM_MODULATIONS];
+	u8	chain_b[BTINTEL_BPMO_NUM_MODULATIONS];
+} __packed;
+
+#define BTINTEL_DDC_ID_SAR_BRDS_LIMIT_2G4	0x0311
+
+#define BTINTEL_DDC_ID_SAR_BPMO_ENABLE		0x0359
+#define BTINTEL_DDC_ID_SAR_BPMO_CHAIN_A		0x035A
+#define BTINTEL_DDC_ID_SAR_BPMO_CHAIN_B		0x035B
+
 /* Bluetooth SAR feature (BRDS), Revision 2 - per-chain sub-band power limits */
 struct btintel_sar_band_limits {
 	u8	subband_2g4;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-30 11:50 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30 11:50 [PATCH v1] Bluetooth: btintel: Add UEFI BRDS and BPMO for PeP Catherine L

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