From 9df9113fead7e7254b64c1bfbd4891f25f7bfbca Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 28 Apr 2026 16:23:06 +0200 Subject: [PATCH 1/9] dt-bindings: mmc: Document support for nvmem-layout Add support for an nvmem-layout subnode under an eMMC hardware partition. This allows the partition to be exposed as an NVMEM provider and its internal layout to be described. For example, an eMMC boot partition can be used to store device-specific information such as a WiFi MAC address. Signed-off-by: Loic Poulain --- .../devicetree/bindings/mmc/mmc-card.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/mmc-card.yaml b/Documentation/devicetree/bindings/mmc/mmc-card.yaml index a61d6c96df759..e01fc82ab8520 100644 --- a/Documentation/devicetree/bindings/mmc/mmc-card.yaml +++ b/Documentation/devicetree/bindings/mmc/mmc-card.yaml @@ -40,6 +40,9 @@ patternProperties: contains: const: fixed-partitions + nvmem-layout: + $ref: /schemas/nvmem/layouts/nvmem-layout.yaml + required: - compatible - reg @@ -86,6 +89,23 @@ examples: read-only; }; }; + + partitions-boot2 { + nvmem-layout { + compatible = "fixed-layout"; + + #address-cells = <1>; + #size-cells = <1>; + + mac-addr@4400 { + reg = <0x4400 0x6>; + }; + + bd-addr@5400 { + reg = <0x5400 0x6>; + }; + }; + }; }; }; From fc658cd5879dcad22e5a6b11cce429008b5babd0 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 28 Apr 2026 16:23:07 +0200 Subject: [PATCH 2/9] arm64: dts: qcom: arduino-imola: Describe boot1 NVMEM layout On Arduino Uno-Q, the eMMC boot1 partition is factory provisioned with device-specific information such as the WiFi MAC address and the Bluetooth BD address. This partition can serve as an alternative to additional non-volatile memory, such as a dedicated EEPROM. The eMMC boot partitions are typically good candidates, as they are realively small, read-only by default (and can be enforced as hardware read-only), and are not affected by board reflashing procedures, which generally target the eMMC user or GP partitions. Describe the corresponding nvmem-layout for the WiFi and Bluetooth addresses. Signed-off-by: Loic Poulain --- .../boot/dts/qcom/qrb2210-arduino-imola.dts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts index bf088fa9807f0..dc85cf94f71ca 100644 --- a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts +++ b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts @@ -409,7 +409,31 @@ no-sdio; no-sd; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + card@0 { + compatible = "mmc-card"; + reg = <0>; + + partitions-boot1 { + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + wifi_mac_addr: mac-addr@4400 { + reg = <0x4400 0x6>; + }; + + bd_addr: bd-addr@5400 { + reg = <0x5400 0x6>; + }; + }; + }; + }; }; &spi5 { From d176d2ab70bdafd6e25aad131bccf93feefd4689 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Tue, 28 Apr 2026 16:23:08 +0200 Subject: [PATCH 3/9] block: implement NVMEM provider On embedded devices using an eMMC it is common that one or more partitions on the eMMC are used to store MAC addresses and Wi-Fi calibration EEPROM data. Allow referencing the partition in device tree for the kernel and Wi-Fi drivers accessing it via the NVMEM layer. Signed-off-by: Daniel Golle Signed-off-by: Loic Poulain --- block/Kconfig | 9 +++ block/Makefile | 1 + block/blk-nvmem.c | 164 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 block/blk-nvmem.c diff --git a/block/Kconfig b/block/Kconfig index 15027963472d7..0b33747e16dc3 100644 --- a/block/Kconfig +++ b/block/Kconfig @@ -209,6 +209,15 @@ config BLK_INLINE_ENCRYPTION_FALLBACK by falling back to the kernel crypto API when inline encryption hardware is not present. +config BLK_NVMEM + bool "Block device NVMEM provider" + depends on OF + depends on NVMEM + help + Allow block devices (or partitions) to act as NVMEM providers, + typically used with eMMC to store MAC addresses or Wi-Fi + calibration data on embedded devices. + source "block/partitions/Kconfig" config BLK_PM diff --git a/block/Makefile b/block/Makefile index 7dce2e44276c4..d7ac389e71902 100644 --- a/block/Makefile +++ b/block/Makefile @@ -36,3 +36,4 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \ blk-crypto-sysfs.o obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED) += holder.o +obj-$(CONFIG_BLK_NVMEM) += blk-nvmem.o diff --git a/block/blk-nvmem.c b/block/blk-nvmem.c new file mode 100644 index 0000000000000..01b67c638a6df --- /dev/null +++ b/block/blk-nvmem.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * block device NVMEM provider + * + * Copyright (c) 2024 Daniel Golle + * + * Useful on devices using a partition on an eMMC for MAC addresses or + * Wi-Fi calibration EEPROM data. + */ + +#include "blk.h" +#include +#include +#include +#include + +/* List of all NVMEM devices */ +static LIST_HEAD(nvmem_devices); +static DEFINE_MUTEX(devices_mutex); + +struct blk_nvmem { + struct nvmem_device *nvmem; + struct device *dev; + struct list_head list; +}; + +static int blk_nvmem_reg_read(void *priv, unsigned int from, + void *val, size_t bytes) +{ + blk_mode_t mode = BLK_OPEN_READ | BLK_OPEN_RESTRICT_WRITES; + unsigned long offs = from & ~PAGE_MASK, to_read; + pgoff_t f_index = from >> PAGE_SHIFT; + struct blk_nvmem *bnv = priv; + size_t bytes_left = bytes; + struct file *bdev_file; + struct folio *folio; + void *p; + int ret = 0; + + bdev_file = bdev_file_open_by_dev(bnv->dev->devt, mode, priv, NULL); + if (!bdev_file) + return -ENODEV; + + if (IS_ERR(bdev_file)) + return PTR_ERR(bdev_file); + + while (bytes_left) { + folio = read_mapping_folio(bdev_file->f_mapping, f_index++, NULL); + if (IS_ERR(folio)) { + ret = PTR_ERR(folio); + goto err_release_bdev; + } + to_read = min_t(unsigned long, bytes_left, PAGE_SIZE - offs); + p = folio_address(folio) + offset_in_folio(folio, offs); + memcpy(val, p, to_read); + offs = 0; + bytes_left -= to_read; + val += to_read; + folio_put(folio); + } + +err_release_bdev: + fput(bdev_file); + + return ret; +} + +static int blk_nvmem_register(struct device *dev) +{ + struct device_node *np = dev_of_node(dev); + struct block_device *bdev = dev_to_bdev(dev); + struct nvmem_config config = {}; + struct blk_nvmem *bnv; + + /* skip devices which do not have a device tree node */ + if (!np) + return 0; + + /* skip devices without an nvmem layout defined */ + if (!of_get_child_by_name(np, "nvmem-layout")) + return 0; + + /* + * skip block device too large to be represented as NVMEM devices + * which are using an 'int' as address + */ + if (bdev_nr_bytes(bdev) > INT_MAX) + return -EFBIG; + + bnv = kzalloc_obj(*bnv); + if (!bnv) + return -ENOMEM; + + config.id = NVMEM_DEVID_NONE; + config.dev = &bdev->bd_device; + config.name = dev_name(&bdev->bd_device); + config.owner = THIS_MODULE; + config.priv = bnv; + config.reg_read = blk_nvmem_reg_read; + config.size = bdev_nr_bytes(bdev); + config.word_size = 1; + config.stride = 1; + config.read_only = true; + config.root_only = true; + config.ignore_wp = true; + config.of_node = to_of_node(dev->fwnode); + + bnv->dev = &bdev->bd_device; + bnv->nvmem = nvmem_register(&config); + if (IS_ERR(bnv->nvmem)) { + dev_err_probe(&bdev->bd_device, PTR_ERR(bnv->nvmem), + "Failed to register NVMEM device\n"); + + kfree(bnv); + return PTR_ERR(bnv->nvmem); + } + + mutex_lock(&devices_mutex); + list_add_tail(&bnv->list, &nvmem_devices); + mutex_unlock(&devices_mutex); + + return 0; +} + +static void blk_nvmem_unregister(struct device *dev) +{ + struct blk_nvmem *bnv_c, *bnv = NULL; + + mutex_lock(&devices_mutex); + list_for_each_entry(bnv_c, &nvmem_devices, list) { + if (bnv_c->dev == dev) { + bnv = bnv_c; + break; + } + } + + if (!bnv) { + mutex_unlock(&devices_mutex); + return; + } + + list_del(&bnv->list); + mutex_unlock(&devices_mutex); + nvmem_unregister(bnv->nvmem); + kfree(bnv); +} + +static struct class_interface blk_nvmem_bus_interface __refdata = { + .class = &block_class, + .add_dev = &blk_nvmem_register, + .remove_dev = &blk_nvmem_unregister, +}; + +static int __init blk_nvmem_init(void) +{ + int ret; + + ret = class_interface_register(&blk_nvmem_bus_interface); + if (ret) + return ret; + + return 0; +} +device_initcall(blk_nvmem_init); From dd26bdfe7213a943ea409ce291701202df83de36 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 28 Apr 2026 16:23:09 +0200 Subject: [PATCH 4/9] dt-bindings: net: wireless: qcom,ath10k: Add NVMEM MAC address cell Add support for an NVMEM cell provider with the standard "mac-address" cell name. This allows the ath10k device to retrieve its MAC address from non-volatile storage such as an EEPROM or an eMMC partition. Signed-off-by: Loic Poulain --- .../devicetree/bindings/net/wireless/qcom,ath10k.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml index c21d66c7cd558..7155d8b15cc14 100644 --- a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml +++ b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml @@ -92,6 +92,16 @@ properties: ieee80211-freq-limit: true + nvmem-cells: + maxItems: 1 + description: + Nvmem data cell that contains a 6 byte MAC address with the most + significant byte first (big-endian). + + nvmem-cell-names: + items: + - const: mac-address + qcom,calibration-data: $ref: /schemas/types.yaml#/definitions/uint8-array description: From c29b594cc68bbd9b2f54cf811ac043a285301417 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 28 Apr 2026 16:23:10 +0200 Subject: [PATCH 5/9] arm64: dts: qcom: arduino-imola: Get WiFi MAC from NVMEM On Arduino Uno-Q, the WiFi MAC address is stored in the eMMC boot1 partition. Point to the appropriate NVMEM cell to retrieve it. Signed-off-by: Loic Poulain Reviewed-by: Konrad Dybcio --- arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts index dc85cf94f71ca..35a30cd6f47d6 100644 --- a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts +++ b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts @@ -581,6 +581,9 @@ qcom,ath10k-calibration-variant = "ArduinoImola"; firmware-name = "qcm2290"; + nvmem-cells = <&wifi_mac_addr>; + nvmem-cell-names = "mac-address"; + status = "okay"; }; From ac64a0e462047310b792b93332d4d87b7fed1ee7 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 28 Apr 2026 16:23:11 +0200 Subject: [PATCH 6/9] dt-bindings: bluetooth: qcom: Add NVMEM BD address cell Add support for an NVMEM cell provider for "local-bd-address", allowing the Bluetooth stack to retrieve controller's BD address from non-volatile storage such as an EEPROM or an eMMC partition. Signed-off-by: Loic Poulain Reviewed-by: Rob Herring (Arm) --- .../bindings/net/bluetooth/qcom,bluetooth-common.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml b/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml index c8e9c55c1afb4..ecb3de65506f7 100644 --- a/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml +++ b/Documentation/devicetree/bindings/net/bluetooth/qcom,bluetooth-common.yaml @@ -22,4 +22,14 @@ properties: description: boot firmware is incorrectly passing the address in big-endian order + nvmem-cells: + maxItems: 1 + description: + Nvmem data cell that contains a 6 byte BD address with the most + significant byte first (big-endian). + + nvmem-cell-names: + items: + - const: local-bd-address + additionalProperties: true From 9fc8733eb5bc9fc315480d355d69d86e35a9a5ec Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 28 Apr 2026 16:23:12 +0200 Subject: [PATCH 7/9] Bluetooth: hci_sync: Add NVMEM-backed BD address retrieval Some devices store the Bluetooth BD address in non-volatile memory, which can be accessed through the NVMEM framework. Similar to Ethernet or WiFi MAC addresses, add support for reading the BD address from a 'local-bd-address' NVMEM cell. As with the device-tree provided BD address, add a quirk to indicate whether a device or platform should attempt to read the address from NVMEM when no valid in-chip address is present. Also add a quirk to indicate if the address is stored in big-endian byte order. Signed-off-by: Loic Poulain --- include/net/bluetooth/hci.h | 18 ++++++++++++ net/bluetooth/hci_sync.c | 56 ++++++++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 572b1c620c5d6..7686466d11092 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -164,6 +164,24 @@ enum { */ HCI_QUIRK_BDADDR_PROPERTY_BROKEN, + /* When this quirk is set, the public Bluetooth address + * initially reported by HCI Read BD Address command + * is considered invalid. The public BD Address can be + * retrieved via a 'local-bd-address' NVMEM cell. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_USE_BDADDR_NVMEM, + + /* When this quirk is set, the Bluetooth Device Address provided by + * the 'local-bd-address' NVMEM is stored in big-endian order. + * + * This quirk can be set before hci_register_dev is called or + * during the hdev->setup vendor callback. + */ + HCI_QUIRK_BDADDR_NVMEM_BE, + /* When this quirk is set, the duplicate filtering during * scanning is based on Bluetooth devices addresses. To allow * RSSI based updates, restart scanning if needed. diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index fd3aacdea512a..f87cb6ae85c3a 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -6,6 +6,7 @@ * Copyright 2023 NXP */ +#include #include #include @@ -3588,6 +3589,54 @@ int hci_powered_update_sync(struct hci_dev *hdev) return 0; } +/** + * hci_dev_get_bd_addr_from_nvmem - Get the Bluetooth Device Address + * (BD_ADDR) for a HCI device from + * an NVMEM cell. + * @hdev: The HCI device + * + * Search for 'local-bd-address' NVMEM cell. + * + * All-zero BD addresses are rejected (unprovisioned). + */ +static int hci_dev_get_bd_addr_from_nvmem(struct hci_dev *hdev) +{ + struct device *dev = hdev->dev.parent; + struct nvmem_cell *cell; + const void *ba; + int err = 0; + size_t len; + + cell = nvmem_cell_get(dev, "local-bd-address"); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + ba = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + + if (IS_ERR(ba)) { + bt_dev_warn(hdev, "Error reading BD address from NVMEM (%ld)\n", + PTR_ERR(ba)); + err = PTR_ERR(ba); + goto done; + } + + if (len != sizeof(bdaddr_t) || !bacmp(ba, BDADDR_ANY)) { + bt_dev_warn(hdev, "NVMEM BD address has incorrect format\n"); + err = -EINVAL; + goto done; + } + + if (hci_test_quirk(hdev, HCI_QUIRK_BDADDR_NVMEM_BE)) + baswap(&hdev->public_addr, (bdaddr_t *)ba); + else + bacpy(&hdev->public_addr, (bdaddr_t *)ba); + +done: + kfree(ba); + return err; +} + /** * hci_dev_get_bd_addr_from_property - Get the Bluetooth Device Address * (BD_ADDR) for a HCI device from @@ -5042,12 +5091,17 @@ static int hci_dev_setup_sync(struct hci_dev *hdev) * its setup callback. */ invalid_bdaddr = hci_test_quirk(hdev, HCI_QUIRK_INVALID_BDADDR) || - hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY); + hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY) || + hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM); if (!ret) { if (hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY) && !bacmp(&hdev->public_addr, BDADDR_ANY)) hci_dev_get_bd_addr_from_property(hdev); + if (hci_test_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM) && + !bacmp(&hdev->public_addr, BDADDR_ANY)) + hci_dev_get_bd_addr_from_nvmem(hdev); + if (invalid_bdaddr && bacmp(&hdev->public_addr, BDADDR_ANY) && hdev->set_bdaddr) { ret = hdev->set_bdaddr(hdev, &hdev->public_addr); From 7a64455c994267c62c97915362a118de22702d61 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 28 Apr 2026 16:23:13 +0200 Subject: [PATCH 8/9] Bluetooth: qca: Set NVMEM BD address quirks when address is invalid When the controller BD address is invalid (zero or default), set the NVMEM quirks to allow retrieving the address from a 'local-bd-address' NVMEM cell. The BD address is often stored alongside the WiFi MAC address in big-endian format, so also set the big-endian quirk. Signed-off-by: Loic Poulain --- drivers/bluetooth/btqca.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c index dda76365726f0..df33eacfd29fa 100644 --- a/drivers/bluetooth/btqca.c +++ b/drivers/bluetooth/btqca.c @@ -721,8 +721,11 @@ static int qca_check_bdaddr(struct hci_dev *hdev, const struct qca_fw_config *co } bda = (struct hci_rp_read_bd_addr *)skb->data; - if (!bacmp(&bda->bdaddr, &config->bdaddr)) + if (!bacmp(&bda->bdaddr, &config->bdaddr)) { hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_PROPERTY); + hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM); + hci_set_quirk(hdev, HCI_QUIRK_BDADDR_NVMEM_BE); + } kfree_skb(skb); From 8fe2955cb9e2a1f9d9450e52f63c2f13c12ef5d5 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 28 Apr 2026 16:23:14 +0200 Subject: [PATCH 9/9] arm64: dts: qcom: arduino-imola: Get Bluetooth BD address from NVMEM On Arduino Uno-Q, the Bluetooth Device address is stored in the eMMC boot1 partition. Point to the appropriate NVMEM cell to retrieve it. Signed-off-by: Loic Poulain Reviewed-by: Konrad Dybcio --- arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts index 35a30cd6f47d6..109fa76e05625 100644 --- a/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts +++ b/arch/arm64/boot/dts/qcom/qrb2210-arduino-imola.dts @@ -536,6 +536,9 @@ vddch0-supply = <&pm4125_l22>; enable-gpios = <&tlmm 87 GPIO_ACTIVE_HIGH>; max-speed = <3000000>; + + nvmem-cells = <&bd_addr>; + nvmem-cell-names = "local-bd-address"; }; };