From aa22340c4638002233be0cc621f3ddac1a5325a0 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Wed, 15 Apr 2026 10:37:38 +0530 Subject: [PATCH 01/35] dts: lemans-evk-u-boot: add override dtsi Add initial support for the lemans EVK platform based on lemans SoC. Define memory layout statically. Signed-off-by: Aswin Murugan Signed-off-by: Sumit Garg --- arch/arm/dts/lemans-evk-u-boot.dtsi | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 arch/arm/dts/lemans-evk-u-boot.dtsi diff --git a/arch/arm/dts/lemans-evk-u-boot.dtsi b/arch/arm/dts/lemans-evk-u-boot.dtsi new file mode 100644 index 000000000000..cdd3d32f61a5 --- /dev/null +++ b/arch/arm/dts/lemans-evk-u-boot.dtsi @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2026, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/ { + /* Will be removed when bootloader updates later */ + memory@80000000 { + device_type = "memory"; + reg = <0x0 0x80000000 0x0 0x3ee00000>, + <0x0 0xc0000000 0x0 0x0fd00000>, + <0xD 0x00000000 0x2 0x54100000>, + <0xA 0x80000000 0x1 0x80000000>, + <0x9 0x00000000 0x1 0x80000000>, + <0x1 0x00000000 0x3 0x00000000>, + <0x0 0xd0000000 0x0 0x01900000>, + <0x0 0xd3500000 0x0 0x2cb00000>; + }; +}; From 2f28485d5c4f3b60e2b543c780c60f9bd09c6b0b Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Tue, 24 Mar 2026 11:22:34 +0530 Subject: [PATCH 02/35] clk: qcom: qcs615: Add SDCC1 and SDCC2 clock support Add clock support for SDCC1 (eMMC) and SDCC2 (SD card) controllers on QCS615 platform. This enables proper clock configuration for both storage interfaces. Reviewed-by: Sumit Garg Reviewed-by: Varadarajan Narayanan Signed-off-by: Balaji Selvanathan --- drivers/clk/qcom/clock-qcom.h | 2 ++ drivers/clk/qcom/clock-qcs615.c | 46 ++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/drivers/clk/qcom/clock-qcom.h b/drivers/clk/qcom/clock-qcom.h index 3a4550d85366..9899cd28aade 100644 --- a/drivers/clk/qcom/clock-qcom.h +++ b/drivers/clk/qcom/clock-qcom.h @@ -14,6 +14,8 @@ #define CFG_CLK_SRC_GPLL0_AUX2 (2 << 8) #define CFG_CLK_SRC_GPLL2 (2 << 8) #define CFG_CLK_SRC_GPLL2_MAIN (2 << 8) +#define CFG_CLK_SRC_GPLL6_OUT_MAIN (2 << 8) +#define CFG_CLK_SRC_GPLL8 (2 << 8) #define CFG_CLK_SRC_GPLL9 (2 << 8) #define CFG_CLK_SRC_GPLL0_ODD (3 << 8) #define CFG_CLK_SRC_GPLL6 (4 << 8) diff --git a/drivers/clk/qcom/clock-qcs615.c b/drivers/clk/qcom/clock-qcs615.c index 2087fc38f63d..094155e20345 100644 --- a/drivers/clk/qcom/clock-qcs615.c +++ b/drivers/clk/qcom/clock-qcs615.c @@ -19,6 +19,34 @@ #define USB30_PRIM_MASTER_CLK_CMD_RCGR 0xf01c #define USB3_PRIM_PHY_AUX_CMD_RCGR 0xf060 +#define SDCC1_APPS_CLK_CMD_RCGR 0x12028 +#define SDCC2_APPS_CLK_CMD_RCGR 0x1400c + +/* + * Frequency tables for SDCC clocks + */ +static const struct freq_tbl ftbl_gcc_sdcc1_apps_clk_src[] = { + F(144000, CFG_CLK_SRC_CXO, 16, 3, 25), + F(400000, CFG_CLK_SRC_CXO, 12, 1, 4), + F(20000000, CFG_CLK_SRC_GPLL0_AUX2, 5, 1, 3), + F(25000000, CFG_CLK_SRC_GPLL0_AUX2, 6, 1, 2), + F(50000000, CFG_CLK_SRC_GPLL0_AUX2, 6, 0, 0), + F(100000000, CFG_CLK_SRC_GPLL0_AUX2, 3, 0, 0), + F(192000000, CFG_CLK_SRC_GPLL6_OUT_MAIN, 2, 0, 0), + F(384000000, CFG_CLK_SRC_GPLL6_OUT_MAIN, 1, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_gcc_sdcc2_apps_clk_src[] = { + F(400000, CFG_CLK_SRC_CXO, 12, 1, 4), + F(19200000, CFG_CLK_SRC_CXO, 1, 0, 0), + F(25000000, CFG_CLK_SRC_GPLL0_AUX2, 12, 0, 0), + F(50000000, CFG_CLK_SRC_GPLL0_AUX2, 6, 0, 0), + F(100000000, CFG_CLK_SRC_GPLL0_AUX2, 3, 0, 0), + F(202000000, CFG_CLK_SRC_GPLL8, 2, 0, 0), + { } +}; + #define GCC_QUPV3_WRAP0_S0_CLK_ENA_BIT BIT(10) #define GCC_QUPV3_WRAP0_S1_CLK_ENA_BIT BIT(11) #define GCC_QUPV3_WRAP0_S2_CLK_ENA_BIT BIT(12) @@ -36,6 +64,7 @@ static ulong qcs615_set_rate(struct clk *clk, ulong rate) { struct msm_clk_priv *priv = dev_get_priv(clk->dev); + const struct freq_tbl *freq; if (clk->id < priv->data->num_clks) debug("%s: %s, requested rate=%ld\n", __func__, @@ -52,6 +81,16 @@ static ulong qcs615_set_rate(struct clk *clk, ulong rate) 5, 0, 0, CFG_CLK_SRC_GPLL0, 8); clk_rcg_set_rate(priv->base, USB3_PRIM_PHY_AUX_CMD_RCGR, 0, 0); return rate; + case GCC_SDCC1_APPS_CLK: + freq = qcom_find_freq(ftbl_gcc_sdcc1_apps_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, SDCC1_APPS_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_SDCC2_APPS_CLK: + freq = qcom_find_freq(ftbl_gcc_sdcc2_apps_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, SDCC2_APPS_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; default: return 0; } @@ -81,7 +120,12 @@ static const struct gate_clk qcs615_clks[] = { GATE_CLK(GCC_QUPV3_WRAP1_S4_CLK, 0x5200c, GCC_QUPV3_WRAP1_S4_CLK_ENA_BIT), GATE_CLK(GCC_QUPV3_WRAP1_S5_CLK, 0x5200c, GCC_QUPV3_WRAP1_S5_CLK_ENA_BIT), GATE_CLK(GCC_DISP_HF_AXI_CLK, 0xb038, BIT(0)), - GATE_CLK(GCC_DISP_AHB_CLK, 0xb032, BIT(0)) + GATE_CLK(GCC_DISP_AHB_CLK, 0xb032, BIT(0)), + GATE_CLK(GCC_SDCC1_AHB_CLK, 0x12008, BIT(0)), + GATE_CLK(GCC_SDCC1_APPS_CLK, 0x12004, BIT(0)), + GATE_CLK(GCC_SDCC1_ICE_CORE_CLK, 0x1200c, BIT(0)), + GATE_CLK(GCC_SDCC2_AHB_CLK, 0x14008, BIT(0)), + GATE_CLK(GCC_SDCC2_APPS_CLK, 0x14004, BIT(0)) }; static int qcs615_enable(struct clk *clk) From ade9dfbd1e4474a0d9e33ac6451460f42f3f13ab Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Tue, 24 Mar 2026 11:22:35 +0530 Subject: [PATCH 03/35] drivers: mmc: uclass: Set removable flag based on device tree property The block device removable flag should reflect whether the MMC device is physically removable (SD card) or soldered (eMMC). This information is specified in the device tree via the "non-removable" property and stored in the MMC_CAP_NONREMOVABLE capability flag. Update the removable flag in the block device descriptor during controller probe to properly reflect the device's removable status. This allows the block layer and upper layers (particularly EFI boot manager) to distinguish between eMMC and SD cards for appropriate handling. The default removable=1 is set in mmc_bind(), and this change overrides it only for non-removable devices after mmc_of_parse() has set the MMC_CAP_NONREMOVABLE capability from the device tree. Reviewed-by: Varadarajan Narayanan Signed-off-by: Balaji Selvanathan --- drivers/mmc/msm_sdhci.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/mmc/msm_sdhci.c b/drivers/mmc/msm_sdhci.c index 66f3cf2de4f0..13fd4ae8a7d7 100644 --- a/drivers/mmc/msm_sdhci.c +++ b/drivers/mmc/msm_sdhci.c @@ -189,6 +189,7 @@ static int msm_sdc_probe(struct udevice *dev) struct sdhci_host *host = &prv->host; u32 core_version, core_minor, core_major; struct reset_ctl bcr_rst; + struct blk_desc *bdesc; u32 caps; int ret; @@ -252,6 +253,12 @@ static int msm_sdc_probe(struct udevice *dev) if (ret) return ret; + if (plat->cfg.host_caps & MMC_CAP_NONREMOVABLE) { + bdesc = mmc_get_blk_desc(&plat->mmc); + if (bdesc) + bdesc->removable = 0; + } + host->mmc = &plat->mmc; host->mmc->dev = dev; host->ops = &msm_sdhci_ops; From 610f08b3e53e8641f7075c84aa00aab185ae2896 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Tue, 21 Apr 2026 18:16:09 +0530 Subject: [PATCH 04/35] configs: qcom_defconfig: Limit MMC transfer size to prevent controller timeout Set CONFIG_SYS_MMC_MAX_BLK_COUNT to 16384 blocks to limit the maximum transfer size per operation. This prevents controller timeouts. Signed-off-by: Balaji Selvanathan --- configs/qcom_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/qcom_defconfig b/configs/qcom_defconfig index 3c8982dafd93..2ec1b71f98c1 100644 --- a/configs/qcom_defconfig +++ b/configs/qcom_defconfig @@ -98,6 +98,7 @@ CONFIG_MISC=y CONFIG_NVMEM=y CONFIG_QCOM_GENI=y CONFIG_I2C_EEPROM=y +CONFIG_SYS_MMC_MAX_BLK_COUNT=16384 CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_ADMA=y CONFIG_MMC_SDHCI_MSM=y From 5b85a5366bb94e4d1ee1056e311a7b250c82bed9 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 13 Feb 2026 16:57:13 +0530 Subject: [PATCH 05/35] misc: Add support for bit fields in NVMEM cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NVMEM cells currently only support byte-level access. Many hardware registers pack multiple fields into single bytes, requiring bit-level granularity. For example, Qualcomm PMIC PON registers store a 7-bit reboot reason field within a single byte, with bit 0 reserved for other purposes. Add support for the optional 'bits' property in NVMEM cell device tree bindings. This property specifies to define a bit field within the cell's register space. Implement bit‑field handling in the driver to max u32 size Example device tree usage: reboot-reason@48 { reg = <0x48 0x01>; bits = <0x01 0x07>; /* 7 bits starting at bit 1 */ }; This reads bits [7:1] from the byte at offset 0x48, leaving bit 0 untouched during write operations. Cells without the 'bits' property continue to work unchanged, ensuring backward compatibility with existing device trees. Signed-off-by: Aswin Murugan --- drivers/misc/nvmem.c | 166 +++++++++++++++++++++++++++++++++++++------ include/nvmem.h | 4 ++ 2 files changed, 148 insertions(+), 22 deletions(-) diff --git a/drivers/misc/nvmem.c b/drivers/misc/nvmem.c index 33e808585657..03538343a29b 100644 --- a/drivers/misc/nvmem.c +++ b/drivers/misc/nvmem.c @@ -12,55 +12,163 @@ #include #include #include +#include +#include +#include -int nvmem_cell_read(struct nvmem_cell *cell, void *buf, size_t size) +/* Maximum supported NVMEM cell size */ +#define MAX_NVMEM_CELL_SIZE sizeof(u32) /* 4 bytes */ + +/** + * nvmem_cell_read_raw() - Read raw bytes from NVMEM cell without bit field extraction + * @cell: NVMEM cell to read from + * @buf: Buffer to store read data + * @size: Size of buffer + * + * This is an internal helper that reads raw bytes from hardware without applying + * bit field extraction. Used by both nvmem_cell_read() and nvmem_cell_write(). + * Caller must validate buffer size before calling this function. + * + * Return: Number of bytes read on success, negative error code on failure + */ +static int nvmem_cell_read_raw(struct nvmem_cell *cell, void *buf, size_t size) { - dev_dbg(cell->nvmem, "%s: off=%u size=%zu\n", __func__, cell->offset, size); - if (size != cell->size) - return -EINVAL; + int ret; + + memset(buf, 0, size); switch (cell->nvmem->driver->id) { case UCLASS_I2C_EEPROM: - return i2c_eeprom_read(cell->nvmem, cell->offset, buf, size); - case UCLASS_MISC: { - int ret = misc_read(cell->nvmem, cell->offset, buf, size); - + ret = i2c_eeprom_read(cell->nvmem, cell->offset, buf, cell->size); + break; + case UCLASS_MISC: + ret = misc_read(cell->nvmem, cell->offset, buf, cell->size); if (ret < 0) return ret; - if (ret != size) + if (ret != cell->size) return -EIO; - return 0; - } + ret = 0; + break; case UCLASS_RTC: - return dm_rtc_read(cell->nvmem, cell->offset, buf, size); + ret = dm_rtc_read(cell->nvmem, cell->offset, buf, cell->size); + break; default: return -ENOSYS; } + + if (ret) + return ret; + + return cell->size; +} + +int nvmem_cell_read(struct nvmem_cell *cell, void *buf, size_t size) +{ + int ret, bytes_needed; + u32 value; + + dev_dbg(cell->nvmem, "%s: off=%u size=%zu\n", __func__, cell->offset, size); + + if (cell->nbits) { + if (size != MAX_NVMEM_CELL_SIZE) { + dev_dbg(cell->nvmem, "bit field requires buffer size %zu, got %zu\n", + MAX_NVMEM_CELL_SIZE, size); + return -EINVAL; + } + + bytes_needed = DIV_ROUND_UP(cell->nbits + cell->bit_offset, BITS_PER_BYTE); + if (bytes_needed > cell->size || bytes_needed > MAX_NVMEM_CELL_SIZE) { + dev_dbg(cell->nvmem, "bit field requires %d bytes, cell size %zu\n", + bytes_needed, cell->size); + return -EINVAL; + } + } else { + if (size != cell->size) { + dev_dbg(cell->nvmem, "buffer size %zu must match cell size %zu\n", + size, cell->size); + return -EINVAL; + } + } + + ret = nvmem_cell_read_raw(cell, buf, size); + if (ret < 0) + return ret; + + if (cell->nbits) { + value = le32_to_cpu(*((__le32 *)buf)); + value >>= cell->bit_offset; + value &= GENMASK(cell->nbits - 1, 0); + *(u32 *)buf = value; + } + + return 0; } int nvmem_cell_write(struct nvmem_cell *cell, const void *buf, size_t size) { + int ret, bytes_needed; + u32 current, value, mask; + dev_dbg(cell->nvmem, "%s: off=%u size=%zu\n", __func__, cell->offset, size); - if (size != cell->size) - return -EINVAL; + + if (cell->nbits) { + if (size != MAX_NVMEM_CELL_SIZE) { + dev_dbg(cell->nvmem, "bit field requires buffer size %zu, got %zu\n", + MAX_NVMEM_CELL_SIZE, size); + return -EINVAL; + } + + bytes_needed = DIV_ROUND_UP(cell->nbits + cell->bit_offset, BITS_PER_BYTE); + if (bytes_needed > cell->size || bytes_needed > MAX_NVMEM_CELL_SIZE) { + dev_dbg(cell->nvmem, "bit field requires %d bytes, cell size %zu\n", + bytes_needed, cell->size); + return -EINVAL; + } + + ret = nvmem_cell_read_raw(cell, ¤t, sizeof(current)); + if (ret < 0) + return ret; + + current = le32_to_cpu(*((__le32 *)¤t)); + value = *(const u32 *)buf; + value &= GENMASK(cell->nbits - 1, 0); + value <<= cell->bit_offset; + + mask = GENMASK(cell->nbits - 1, 0) << cell->bit_offset; + + current = (current & ~mask) | value; + buf = ¤t; + } else { + if (size != cell->size) { + dev_dbg(cell->nvmem, "buffer size %zu must match cell size %zu\n", + size, cell->size); + return -EINVAL; + } + } switch (cell->nvmem->driver->id) { case UCLASS_I2C_EEPROM: - return i2c_eeprom_write(cell->nvmem, cell->offset, buf, size); - case UCLASS_MISC: { - int ret = misc_write(cell->nvmem, cell->offset, buf, size); - + ret = i2c_eeprom_write(cell->nvmem, cell->offset, buf, cell->size); + break; + case UCLASS_MISC: + ret = misc_write(cell->nvmem, cell->offset, buf, cell->size); if (ret < 0) return ret; - if (ret != size) + if (ret != cell->size) return -EIO; - return 0; - } + ret = 0; + break; case UCLASS_RTC: - return dm_rtc_write(cell->nvmem, cell->offset, buf, size); + ret = dm_rtc_write(cell->nvmem, cell->offset, buf, cell->size); + break; default: return -ENOSYS; } + + if (ret) + return ret; + + return 0; } /** @@ -128,6 +236,20 @@ int nvmem_cell_get_by_index(struct udevice *dev, int index, cell->offset = offset; cell->size = size; + + ret = ofnode_read_u32_index(args.node, "bits", 0, &cell->bit_offset); + if (ret) { + cell->bit_offset = 0; + cell->nbits = 0; + } else { + ret = ofnode_read_u32_index(args.node, "bits", 1, &cell->nbits); + if (ret) + return -EINVAL; + + if (cell->bit_offset + cell->nbits > cell->size * 8) + return -EINVAL; + } + return 0; } diff --git a/include/nvmem.h b/include/nvmem.h index e6a8a98828b3..dd82122f16fa 100644 --- a/include/nvmem.h +++ b/include/nvmem.h @@ -26,11 +26,15 @@ * @nvmem: The backing storage device * @offset: The offset of the cell from the start of @nvmem * @size: The size of the cell, in bytes + * @bit_offset: Bit offset within the cell (0 for byte-level access) + * @nbits: Number of bits to use (0 for byte-level access) */ struct nvmem_cell { struct udevice *nvmem; unsigned int offset; size_t size; + unsigned int bit_offset; + unsigned int nbits; }; struct udevice; From f15890e67be2339ac3231a42609352deb3d9bb59 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 13 Feb 2026 16:57:14 +0530 Subject: [PATCH 06/35] misc: qcom: Add Qualcomm SPMI SDAM NVMEM driver Qualcomm PMICs include SDAM (Shared Direct Access Memory) regions which are used to store persistent data like reboot reasons that must survive across reboots. Without this driver, U-Boot cannot access PMIC storage, preventing reboot-to-bootloader functionality and other features that rely on persistent state. Add qcom-spmi-sdam driver that: - Probes SDAM regions from device tree compatible "qcom,spmi-sdam" - Implements NVMEM provider interface for standard cell-based access - Uses SPMI register read/write operations for data access This enables reboot-mode and other subsystems to access PMIC storage through standard NVMEM APIs. Signed-off-by: Aswin Murugan --- drivers/misc/Kconfig | 8 ++ drivers/misc/Makefile | 1 + drivers/misc/qcom-spmi-sdam.c | 202 ++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 drivers/misc/qcom-spmi-sdam.c diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index ea785793d18b..c00aa11d65d5 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -93,6 +93,14 @@ config QCOM_GENI for providing a common interface for various peripherals like UART, I2C, SPI, etc. +config QCOM_SPMI_SDAM + bool "Qualcomm SPMI SDAM NVMEM driver" + depends on MISC && NVMEM && SPMI + help + Enable support for Qualcomm SPMI SDAM (Shared Direct Access Memory) blocks + as NVMEM providers. This driver support accessing SDAM blocks in PMICs + for reboot reason functionality and other NVMEM use cases. + config ROCKCHIP_EFUSE bool "Rockchip e-fuse support" depends on MISC diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index e2170212e5ad..13800cc8b7fd 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_QFW_SMBIOS) += qfw_smbios.o obj-$(CONFIG_SANDBOX) += qfw_sandbox.o endif obj-$(CONFIG_QCOM_GENI) += qcom_geni.o +obj-$(CONFIG_QCOM_SPMI_SDAM) += qcom-spmi-sdam.o obj-$(CONFIG_$(PHASE_)ROCKCHIP_EFUSE) += rockchip-efuse.o obj-$(CONFIG_$(PHASE_)ROCKCHIP_OTP) += rockchip-otp.o obj-$(CONFIG_$(PHASE_)ROCKCHIP_IODOMAIN) += rockchip-io-domain.o diff --git a/drivers/misc/qcom-spmi-sdam.c b/drivers/misc/qcom-spmi-sdam.c new file mode 100644 index 000000000000..f987c19deb45 --- /dev/null +++ b/drivers/misc/qcom-spmi-sdam.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm SPMI SDAM NVMEM driver + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include + +#define PID_SHIFT 8 +#define PID_MASK (0xFF << PID_SHIFT) +#define REG_MASK 0xFF +#define SDAM_SIZE 0x100 + +struct qcom_sdam_priv { + u32 base; + u32 size; + u32 pmic_usid; + struct udevice *spmi_dev; +}; + +/** + * qcom_sdam_find_spmi_pmic() - Find SPMI controller and PMIC USID + * @dev: SDAM device + * @spmi_dev: Returns SPMI controller device + * @pmic_usid: Returns PMIC USID for SPMI access + * + * Walks up the device tree to find the PMIC parent and SPMI controller. + * Supports both direct SDAM under PMIC and virtual NVMEM under PON. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_sdam_find_spmi_pmic(struct udevice *dev, + struct udevice **spmi_dev, + u32 *pmic_usid) +{ + struct udevice *pmic_dev = dev->parent; + int ret; + + if (!pmic_dev) { + dev_err(dev, "No parent device found\n"); + return -ENODEV; + } + + ret = dev_read_u32_index(pmic_dev, "reg", 0, pmic_usid); + if (ret) { + dev_err(dev, "Could not read PMIC USID: %d\n", ret); + return ret; + } + + *spmi_dev = pmic_dev->parent; + if (!*spmi_dev || (*spmi_dev)->uclass->uc_drv->id != UCLASS_SPMI) { + dev_err(dev, "Could not find SPMI controller\n"); + return -ENODEV; + } + + dev_dbg(dev, "Found PMIC USID=%d, SPMI controller=%s\n", + *pmic_usid, (*spmi_dev)->name); + + return 0; +} + +/** + * qcom_sdam_read() - Read data from SDAM/NVMEM region + * @dev: MISC device (SDAM) + * @offset: Offset within SDAM/NVMEM region + * @buf: Buffer to read data into + * @size: Number of bytes to read + * + * Uses the same SPMI register access pattern as pmic_qcom.c driver + * for consistency and reliability. This function is called by the + * NVMEM subsystem via misc_read(). + * + * Return: number of bytes read on success, negative error code on failure + */ +static int qcom_sdam_read(struct udevice *dev, int offset, + void *buf, int size) +{ + struct qcom_sdam_priv *priv = dev_get_priv(dev); + u8 *buffer = buf; + int ret; + + if (offset + size > priv->size) + return -EINVAL; + + for (size_t i = 0; i < size; i++) { + u32 reg = priv->base + offset + i; + + ret = spmi_reg_read(priv->spmi_dev, priv->pmic_usid, + (reg & PID_MASK) >> PID_SHIFT, + reg & REG_MASK); + if (ret < 0) { + dev_err(dev, "SPMI read failed at 0x%x: %d\n", reg, ret); + return ret; + } + buffer[i] = ret; + + dev_dbg(dev, "Read 0x%02x from 0x%x (PID=0x%02x REG=0x%02x)\n", + buffer[i], reg, (reg & PID_MASK) >> PID_SHIFT, reg & REG_MASK); + } + + return size; +} + +/** + * qcom_sdam_write() - Write data to SDAM/NVMEM region + * @dev: MISC device (SDAM) + * @offset: Offset within SDAM/NVMEM region + * @buf: Buffer containing data to write + * @size: Number of bytes to write + * + * Uses the same SPMI register access pattern as pmic_qcom.c driver + * for consistency and reliability. This function is called by the + * NVMEM subsystem via misc_write(). + * + * Return: number of bytes written on success, negative error code on failure + */ +static int qcom_sdam_write(struct udevice *dev, int offset, + const void *buf, int size) +{ + struct qcom_sdam_priv *priv = dev_get_priv(dev); + const u8 *buffer = buf; + int ret; + + if (offset + size > priv->size) + return -EINVAL; + + for (size_t i = 0; i < size; i++) { + u32 reg = priv->base + offset + i; + + ret = spmi_reg_write(priv->spmi_dev, priv->pmic_usid, + (reg & PID_MASK) >> PID_SHIFT, + reg & REG_MASK, + buffer[i]); + if (ret < 0) { + dev_err(dev, "SPMI write failed at 0x%x: %d\n", reg, ret); + return ret; + } + + dev_dbg(dev, "Wrote 0x%02x to 0x%x (PID=0x%02x REG=0x%02x)\n", + buffer[i], reg, (reg & PID_MASK) >> PID_SHIFT, reg & REG_MASK); + } + + return size; +} + +static const struct misc_ops qcom_sdam_ops = { + .read = qcom_sdam_read, + .write = qcom_sdam_write, +}; + +/** + * qcom_sdam_probe() - Probe SDAM device and register as NVMEM provider + * @dev: SDAM device + * + * Handles both real SDAM blocks and virtual NVMEM under PON blocks. + * For virtual NVMEM, adds the parent PON base address to the offset. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_sdam_probe(struct udevice *dev) +{ + struct qcom_sdam_priv *priv = dev_get_priv(dev); + fdt_addr_t base; + int ret; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) { + dev_err(dev, "Could not read base address\n"); + return -EINVAL; + } + + priv->base = base; + priv->size = SDAM_SIZE; + + ret = qcom_sdam_find_spmi_pmic(dev, &priv->spmi_dev, &priv->pmic_usid); + if (ret) + return ret; + + dev_dbg(dev, "SDAM base=0x%x size=0x%x PMIC_USID=%d\n", + priv->base, priv->size, priv->pmic_usid); + + return 0; +} + +static const struct udevice_id qcom_sdam_ids[] = { + { .compatible = "qcom,spmi-sdam" }, + { } +}; + +U_BOOT_DRIVER(qcom_spmi_sdam) = { + .name = "qcom-spmi-sdam", + .id = UCLASS_MISC, + .of_match = qcom_sdam_ids, + .probe = qcom_sdam_probe, + .ops = &qcom_sdam_ops, + .priv_auto = sizeof(struct qcom_sdam_priv), +}; From 636913f73f017a472b98d05626dfc93248f39d41 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Wed, 15 Apr 2026 14:33:09 +0530 Subject: [PATCH 07/35] reboot-mode: add REBOOT_MODE_ENV_UPDATE option Add REBOOT_MODE_ENV_UPDATE Kconfig option and implement automatic reboot-mode env variable update at last stage init. When enabled, the reboot-mode uclass registers an EVT_LAST_STAGE_INIT event handler that probes the first reboot-mode device and calls dm_reboot_mode_update() to set the reboot-mode environment variable. EVT_LAST_STAGE_INIT fires after the environment is fully initialized. Signed-off-by: Aswin Murugan --- drivers/reboot-mode/Kconfig | 8 +++++++ drivers/reboot-mode/reboot-mode-uclass.c | 29 ++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/drivers/reboot-mode/Kconfig b/drivers/reboot-mode/Kconfig index 72b33d712235..6e8825f1e67d 100644 --- a/drivers/reboot-mode/Kconfig +++ b/drivers/reboot-mode/Kconfig @@ -39,4 +39,12 @@ config REBOOT_MODE_NVMEM Use any kind of non-volatile memory (EEPROM, RTC, etc) to control the reboot mode. +config REBOOT_MODE_ENV_UPDATE + bool "Automatically update reboot-mode env variable on boot" + depends on DM_REBOOT_MODE + help + When enabled, the reboot-mode uclass will automatically call + dm_reboot_mode_update() on the first reboot-mode device at last + stage init. + endmenu diff --git a/drivers/reboot-mode/reboot-mode-uclass.c b/drivers/reboot-mode/reboot-mode-uclass.c index 7cbe02eb4ed4..976544d28107 100644 --- a/drivers/reboot-mode/reboot-mode-uclass.c +++ b/drivers/reboot-mode/reboot-mode-uclass.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -116,9 +117,33 @@ int dm_reboot_mode_pre_probe(struct udevice *dev) return 0; } +/* + * reboot_mode_last_stage_init() - Update reboot-mode env variable at last + * stage init. + * + * Called via EVT_LAST_STAGE_INIT, which fires after the environment is fully + * initialized. + * + */ +static int reboot_mode_last_stage_init(void) +{ + struct udevice *dev; + int ret; + + if (!CONFIG_IS_ENABLED(REBOOT_MODE_ENV_UPDATE)) + return 0; + + ret = uclass_first_device_err(UCLASS_REBOOT_MODE, &dev); + if (ret) + return 0; + + return dm_reboot_mode_update(dev); +} +EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, reboot_mode_last_stage_init); + UCLASS_DRIVER(reboot_mode) = { - .name = "reboot-mode", - .id = UCLASS_REBOOT_MODE, + .name = "reboot-mode", + .id = UCLASS_REBOOT_MODE, .pre_probe = dm_reboot_mode_pre_probe, .per_device_plat_auto = sizeof(struct reboot_mode_uclass_platdata), From a3052fdaf73e1b5275cc261005ae0dac5a9619e6 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Wed, 15 Apr 2026 14:39:36 +0530 Subject: [PATCH 08/35] mach-snapdragon: enter fastboot on bootloader reboot reason Update preboot to check the reboot-mode environment variable and automatically enter fastboot mode when the reboot reason is "bootloader". This enables 'adb reboot bootloader' functionality on Qualcomm platforms. The reboot-mode variable is set by the reboot-mode uclass via EVT_LAST_STAGE_INIT before preboot runs. Signed-off-by: Aswin Murugan --- board/qualcomm/default.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/qualcomm/default.env b/board/qualcomm/default.env index dbf6f4e72600..429d71d57f3c 100644 --- a/board/qualcomm/default.env +++ b/board/qualcomm/default.env @@ -1,7 +1,7 @@ stdin=serial,button-kbd stdout=serial,vidconsole stderr=serial,vidconsole -preboot=scsi scan; usb start +preboot=scsi scan; usb start; if test "${reboot-mode}" = "bootloader"; then run fastboot; fi fastboot=fastboot -l $fastboot_addr_r usb 0 do_boot=bootefi bootmgr bootmenu_0=Boot first available device=run do_boot From 4a8f4f6312a31a5c7089cffabbd057004a92e58f Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 13 Feb 2026 16:57:17 +0530 Subject: [PATCH 09/35] qcom_defconfig: Enable reboot-mode support in qcom_defconfig Enable reboot-mode functionality for all Qualcomm platforms that define reboot-mode device tree nodes. The drivers gracefully handle platforms without reboot-mode configuration, making it safe to enable globally. Add config options: CONFIG_DM_REBOOT_MODE=y - Core reboot-mode framework CONFIG_REBOOT_MODE_NVMEM=y - NVMEM-based storage backend CONFIG_QCOM_SPMI_SDAM=y - Qualcomm PMIC SDAM/PON access CONFIG_REBOOT_MODE_ENV_UPDATE=y - Auto-update reboot-mode env Signed-off-by: Aswin Murugan --- configs/qcom_defconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/configs/qcom_defconfig b/configs/qcom_defconfig index 2ec1b71f98c1..707ddd493208 100644 --- a/configs/qcom_defconfig +++ b/configs/qcom_defconfig @@ -119,6 +119,7 @@ CONFIG_PINCONF=y CONFIG_PINCTRL_QCOM_GENERIC=y CONFIG_DM_PMIC=y CONFIG_PMIC_QCOM=y +CONFIG_DM_REBOOT_MODE=y CONFIG_DM_REGULATOR=y CONFIG_DM_REGULATOR_FIXED=y CONFIG_DM_REGULATOR_QCOM_RPMH=y @@ -131,6 +132,9 @@ CONFIG_SOC_QCOM=y CONFIG_QCOM_COMMAND_DB=y CONFIG_QCOM_RPMH_POWER_DOMAIN=y CONFIG_QCOM_RPMH=y +CONFIG_QCOM_SPMI_SDAM=y +CONFIG_REBOOT_MODE_ENV_UPDATE=y +CONFIG_REBOOT_MODE_NVMEM=y CONFIG_SPMI_MSM=y CONFIG_SYSINFO=y CONFIG_SYSINFO_SMBIOS=y From ea61566bd1fdebd8efe3af9e75c80ec2e6851ff4 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Mon, 30 Mar 2026 17:57:47 +0530 Subject: [PATCH 10/35] test: dm: add comprehensive tests for NVMEM bit field operations Add a mock I2C EEPROM device (nvmem-test@50) to the sandbox device tree to support NVMEM bit field operation testing. Add test coverage for NVMEM bit field read and write operations to validate the new bit field support in the NVMEM subsystem. Test cases include: - 1-byte cell with 7-bit field (Qualcomm SDAM reboot reason use case) - 2-byte cell with 12-bit field spanning a byte boundary - 4-byte cell without a bit field (legacy byte-level access) - 4-byte cell with a 16-bit field in the upper 2 bytes Error validation tests cover: - Bit field exceeding the cell size - Bit field exceeding the 32-bit maximum - Invalid bit_offset and nbits combinations - Buffer size mismatch in non-bit-field mode The tests verify: - Correct bit extraction during read operations - Read-modify-write behavior preserving unrelated bits - Proper error handling for invalid configurations Signed-off-by: Aswin Murugan --- arch/sandbox/dts/test.dts | 12 +++ test/dm/Makefile | 1 + test/dm/nvmem.c | 160 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 test/dm/nvmem.c diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index ac92ebf1afd7..383b5ccf6df4 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -961,6 +961,11 @@ sandbox,filename = "i2c.bin"; sandbox,size = <256>; }; + emul_nvmem_test: emul-nvmem-test { + compatible = "sandbox,i2c-eeprom"; + sandbox,filename = "nvmem-test.bin"; + sandbox,size = <256>; + }; emul0: emul0 { compatible = "sandbox,i2c-rtc-emul"; }; @@ -978,6 +983,13 @@ reg = <0x41>; sandbox,emul = <&emul_pmic1>; }; + + /* Mock NVMEM device for bit field testing */ + nvmem-test@50 { + reg = <0x50>; + compatible = "i2c-eeprom"; + sandbox,emul = <&emul_nvmem_test>; + }; }; i3c0 { diff --git a/test/dm/Makefile b/test/dm/Makefile index 771b703b737d..c60bb077e10a 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_CMD_MUX) += mux-cmd.o obj-$(CONFIG_MULTIPLEXER) += mux-emul.o obj-$(CONFIG_MUX_MMIO) += mux-mmio.o obj-y += fdtdec.o +obj-$(CONFIG_NVMEM) += nvmem.o obj-$(CONFIG_MTD_RAW_NAND) += nand.o obj-$(CONFIG_UT_DM) += nop.o obj-y += ofnode.o diff --git a/test/dm/nvmem.c b/test/dm/nvmem.c new file mode 100644 index 000000000000..dd8d0151d2b9 --- /dev/null +++ b/test/dm/nvmem.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for NVMEM bit field support + */ + +#include +#include +#include +#include +#include +#include + +static int nvmem_test_write_raw(struct udevice *dev, uint offset, + const void *buf, uint size) +{ + return i2c_eeprom_write(dev, offset, buf, size); +} + +static int nvmem_test_read_raw(struct udevice *dev, uint offset, + void *buf, uint size) +{ + return i2c_eeprom_read(dev, offset, buf, size); +} + +/* Test NVMEM bit field operations */ +static int dm_test_nvmem_bitfield(struct unit_test_state *uts) +{ + struct udevice *nvmem_dev; + struct nvmem_cell cell; + u32 value; + u8 hw_value_u8; + u32 hw_value_u32; + + ut_assertok(uclass_get_device_by_name(UCLASS_I2C_EEPROM, + "nvmem-test@50", &nvmem_dev)); + + cell.nvmem = nvmem_dev; + + /* Test reg = <0x0 0x1>; bits = <1 7>: */ + cell.offset = 0x0; + cell.size = 1; + cell.bit_offset = 1; + cell.nbits = 7; + hw_value_u8 = 0x01; + ut_assertok(nvmem_test_write_raw(nvmem_dev, cell.offset, &hw_value_u8, 1)); + value = 0x7f; + ut_assertok(nvmem_cell_write(&cell, &value, sizeof(value))); + value = 0; + ut_assertok(nvmem_cell_read(&cell, &value, sizeof(value))); + ut_asserteq(0x7f, value); + ut_assertok(nvmem_test_read_raw(nvmem_dev, cell.offset, &hw_value_u8, 1)); + ut_asserteq(0xff, hw_value_u8); + + /* Test reg = <0x18 0x4>; bits = <4 12>: Spanning byte boundary */ + cell.offset = 0x18; + cell.size = 4; + cell.bit_offset = 4; + cell.nbits = 12; + hw_value_u32 = 0x0000000f; + ut_assertok(nvmem_test_write_raw(nvmem_dev, cell.offset, (u8 *)&hw_value_u32, 4)); + value = 0xfff; + ut_assertok(nvmem_cell_write(&cell, &value, sizeof(value))); + value = 0; + ut_assertok(nvmem_cell_read(&cell, &value, sizeof(value))); + ut_asserteq(0xfff, value); + ut_assertok(nvmem_test_read_raw(nvmem_dev, cell.offset, (u8 *)&hw_value_u32, 4)); + ut_asserteq(0x0000ffff, hw_value_u32); + + /* Test reg = <0x9 0x4>: Full 4-byte access without bit field */ + cell.offset = 0x9; + cell.bit_offset = 0; + cell.nbits = 0; + value = 0x12345678; + ut_assertok(nvmem_cell_write(&cell, &value, sizeof(value))); + value = 0; + ut_assertok(nvmem_cell_read(&cell, &value, sizeof(value))); + ut_asserteq(0x12345678, value); + + /* Test reg = <0xc 0x4>; bits = <16 16>: Upper 2 bytes */ + cell.offset = 0xc; + cell.bit_offset = 16; + cell.nbits = 16; + hw_value_u32 = 0x0000ffff; + ut_assertok(nvmem_test_write_raw(nvmem_dev, cell.offset, (u8 *)&hw_value_u32, 4)); + value = 0xffff; + ut_assertok(nvmem_cell_write(&cell, &value, sizeof(value))); + value = 0; + ut_assertok(nvmem_cell_read(&cell, &value, sizeof(value))); + ut_asserteq(0xffff, value); + ut_assertok(nvmem_test_read_raw(nvmem_dev, cell.offset, (u8 *)&hw_value_u32, 4)); + ut_asserteq(0xffffffff, hw_value_u32); + + return 0; +} +DM_TEST(dm_test_nvmem_bitfield, + UTF_PROBE_TEST | UTF_SCAN_FDT | UTF_FLAT_TREE); + +/* Test NVMEM error handling for invalid configurations */ +static int dm_test_nvmem_bitfield_errors(struct unit_test_state *uts) +{ + struct udevice *nvmem_dev; + struct nvmem_cell cell; + u32 value; + int ret; + + ut_assertok(uclass_get_device_by_name(UCLASS_I2C_EEPROM, + "nvmem-test@50", &nvmem_dev)); + + /* Test bit field exceeding cell size */ + cell.nvmem = nvmem_dev; + cell.offset = 0xd; + cell.size = 1; + cell.bit_offset = 0; + cell.nbits = 9; + + value = 0xff; + ret = nvmem_cell_write(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + ret = nvmem_cell_read(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + /* Test bit field exceeding 32 bits */ + cell.size = 4; + cell.bit_offset = 0; + cell.nbits = 33; + + ret = nvmem_cell_write(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + ret = nvmem_cell_read(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + /* Test invalid bit_offset + nbits */ + cell.size = 1; + cell.bit_offset = 7; + cell.nbits = 2; + + ret = nvmem_cell_write(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + ret = nvmem_cell_read(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + /* Test nbits=0 requires buffer size == cell size */ + cell.size = 1; + cell.bit_offset = 0; + cell.nbits = 0; + + value = 0xff; + ret = nvmem_cell_write(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + ret = nvmem_cell_read(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + return 0; +} +DM_TEST(dm_test_nvmem_bitfield_errors, + UTF_PROBE_TEST | UTF_SCAN_FDT | UTF_FLAT_TREE); From f7b98b12ff408a37d24a4ef4704cd5383e615c6e Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Mon, 30 Mar 2026 18:09:30 +0530 Subject: [PATCH 11/35] misc: update API documentation for bit field support in NVMEM Update the nvmem_cell_read() and nvmem_cell_write() documentation to describe the new bit field operation mode. The documentation now clearly explains: For bit field mode (nbits > 0): - Read: extracts the bit field from raw hardware bytes - Write: performs read-modify-write to preserve other bits - Requirements: buffer size must be sizeof(u32), cell size <= 4 bytes For non-bit-field mode (nbits == 0): - Read/Write: direct byte-level access - Requirements: buffer size must equal the cell size This helps developers understand when to use each mode and the associated buffer size requirements. Signed-off-by: Aswin Murugan --- include/nvmem.h | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/include/nvmem.h b/include/nvmem.h index dd82122f16fa..c3d845c3a7eb 100644 --- a/include/nvmem.h +++ b/include/nvmem.h @@ -43,13 +43,27 @@ struct udevice; /** * nvmem_cell_read() - Read the value of an nvmem cell - * @cell: The nvmem cell to read + * @cell: The nvmem cell to read, containing: + * - @cell->offset: Byte offset within the NVMEM device + * - @cell->size: Size of the cell in bytes + * - @cell->nbits: Number of bits to extract (0 = read entire cell) + * - @cell->bit_offset: Starting bit position for extraction * @buf: The buffer to read into * @size: The size of @buf * + * For cells with bit fields (@cell->nbits > 0), this function: + * - Reads the raw bytes from @cell->offset in hardware + * - Extracts the bit field using @cell->bit_offset and @cell->nbits + * - Returns the extracted value in @buf + * - Requires @size == sizeof(u32) and @cell->size <= sizeof(u32) + * + * For cells without bit fields (@cell->nbits == 0): + * - Reads raw bytes directly from @cell->offset + * - Requires @size == @cell->size + * * Return: * * 0 on success - * * -EINVAL if @buf is not the same size as @cell. + * * -EINVAL if @size doesn't match requirements * * -ENOSYS if CONFIG_NVMEM is disabled * * A negative error if there was a problem reading the underlying storage */ @@ -57,13 +71,27 @@ int nvmem_cell_read(struct nvmem_cell *cell, void *buf, size_t size); /** * nvmem_cell_write() - Write a value to an nvmem cell - * @cell: The nvmem cell to write + * @cell: The nvmem cell to write, containing: + * - @cell->offset: Byte offset within the NVMEM device + * - @cell->size: Size of the cell in bytes + * - @cell->nbits: Number of bits to write (0 = write entire cell) + * - @cell->bit_offset: Starting bit position for insertion * @buf: The buffer to write from * @size: The size of @buf * + * For cells with bit fields (@cell->nbits > 0), this function: + * - Performs Read-Modify-Write to preserve other bits at @cell->offset + * - Masks and shifts the value to @cell->bit_offset position + * - Merges with existing bits outside the @cell->nbits field + * - Requires @size == sizeof(u32) and @cell->size <= sizeof(u32) + * + * For cells without bit fields (@cell->nbits == 0): + * - Writes raw bytes directly to @cell->offset + * - Requires @size == @cell->size + * * Return: * * 0 on success - * * -EINVAL if @buf is not the same size as @cell + * * -EINVAL if @size doesn't match requirements * * -ENOSYS if @cell is read-only, or if CONFIG_NVMEM is disabled * * A negative error if there was a problem writing the underlying storage */ From 572808b75aa1e32f7cfa67138fd4a6f67feab410 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 13 Feb 2026 16:57:16 +0530 Subject: [PATCH 12/35] dts: qcs615-ride-u-boot.dtsi: Add reboot-mode support The PM8150 PMIC on this platform uses the older PON (Power On) register architecture rather than dedicated SDAM regions found in newer PMIC generations. To enable reboot-mode functionality with the unified NVMEM-based approach, add a compatibility wrapper that exposes PON registers through the SDAM NVMEM interface. Add device tree configuration: - NVMEM node with compatible "qcom,spmi-sdam" wrapping PON registers - Uses 'ranges' property to map the PON register block at offset 0x800 - NVMEM cell at offset 0x8F (PON_SOFT_RB_SPARE register) - 7-bit field (bits [7:1]) for reboot reason, preserving bit 0 - Mode mappings: bootloader=0x02, recovery=0x01 This wrapper allows the SDAM NVMEM driver to access PON registers transparently, providing a unified interface for both PON-based (older) and SDAM-based (newer) PMIC generations. The PON_SOFT_RB_SPARE register persists across warm resets and is automatically cleared on power cycle. This will be maintained in uboot untill it is upstreamed in kernel dts Signed-off-by: Aswin Murugan --- arch/arm/dts/qcs615-ride-u-boot.dtsi | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arch/arm/dts/qcs615-ride-u-boot.dtsi b/arch/arm/dts/qcs615-ride-u-boot.dtsi index 68fffc70fcb4..687e9a67a9f0 100644 --- a/arch/arm/dts/qcs615-ride-u-boot.dtsi +++ b/arch/arm/dts/qcs615-ride-u-boot.dtsi @@ -11,4 +11,30 @@ <0x0 0xc0000000 0x0 0xc0000000>, <0x1 0x80000000 0x1 0x00000000>; }; + + reboot-mode { + compatible = "nvmem-reboot-mode"; + nvmem-cells = <&reboot_reason>; + nvmem-cell-names = "reboot-mode"; + + mode-bootloader = <0x02>; + mode-recovery = <0x01>; + }; +}; + +&pm8150_0 { + /* Virtual NVMEM node for PON-based reboot reason storage */ + nvram@800 { + compatible = "qcom,spmi-sdam"; + reg = <0x800>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x00 0x800 0x100>; + + /* Reboot reason cell at PON_SOFT_RB_SPARE (0x88F) */ + reboot_reason: reboot-reason@8f { + reg = <0x8f 0x1>; + bits = <1 7>; + }; + }; }; From 162c1b8cf258e3bda9c54af13e285cb9b25b2105 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 10:25:13 +0530 Subject: [PATCH 13/35] clk: stub: Sort compatible strings alphabetically Reorder compatible strings in stub_clk_ids to maintain alphabetical order for easier maintenance. Signed-off-by: Balaji Selvanathan --- drivers/clk/clk-stub.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clk/clk-stub.c b/drivers/clk/clk-stub.c index 117266ac7789..ddcaaa00d91e 100644 --- a/drivers/clk/clk-stub.c +++ b/drivers/clk/clk-stub.c @@ -50,10 +50,10 @@ static struct clk_ops stub_clk_ops = { static const struct udevice_id stub_clk_ids[] = { { .compatible = "qcom,rpmcc" }, - { .compatible = "qcom,sdm670-rpmh-clk" }, - { .compatible = "qcom,sdm845-rpmh-clk" }, { .compatible = "qcom,sc7180-rpmh-clk" }, { .compatible = "qcom,sc7280-rpmh-clk" }, + { .compatible = "qcom,sdm670-rpmh-clk" }, + { .compatible = "qcom,sdm845-rpmh-clk" }, { .compatible = "qcom,sm6350-rpmh-clk" }, { .compatible = "qcom,sm8150-rpmh-clk" }, { .compatible = "qcom,sm8250-rpmh-clk" }, From 76d052fd22c3761c56fd2b627ddf93770f64e9d7 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:10:59 +0530 Subject: [PATCH 14/35] clk: qcom: clk-stub: Add compatibles for QCS615/SA8775P Add RPMH clock compatible strings for QCS615 and SA8775P SoCs to enable clock framework support on these platforms. Signed-off-by: Balaji Selvanathan --- drivers/clk/clk-stub.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/clk/clk-stub.c b/drivers/clk/clk-stub.c index ddcaaa00d91e..4a6c71016da2 100644 --- a/drivers/clk/clk-stub.c +++ b/drivers/clk/clk-stub.c @@ -49,7 +49,9 @@ static struct clk_ops stub_clk_ops = { }; static const struct udevice_id stub_clk_ids[] = { + { .compatible = "qcom,qcs615-rpmh-clk" }, { .compatible = "qcom,rpmcc" }, + { .compatible = "qcom,sa8775p-rpmh-clk" }, { .compatible = "qcom,sc7180-rpmh-clk" }, { .compatible = "qcom,sc7280-rpmh-clk" }, { .compatible = "qcom,sdm670-rpmh-clk" }, From f8fadd1bda84e19c9fe0e71adf8c050bb0187d25 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:26:37 +0530 Subject: [PATCH 15/35] clk: qcom: sa8775p: Add UFS clock support Add UFS clock support for SA8775P including register definitions, rate configuration, and gate clocks. Signed-off-by: Balaji Selvanathan --- drivers/clk/qcom/clock-sa8775p.c | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/drivers/clk/qcom/clock-sa8775p.c b/drivers/clk/qcom/clock-sa8775p.c index 4957abf6f589..7eec4aeae489 100644 --- a/drivers/clk/qcom/clock-sa8775p.c +++ b/drivers/clk/qcom/clock-sa8775p.c @@ -19,6 +19,11 @@ #define USB30_PRIM_MASTER_CLK_CMD_RCGR 0x1b028 #define USB3_PRIM_PHY_AUX_CMD_RCGR 0x1b06c +#define UFS_PHY_AXI_CLK_CMD_RCGR 0x8302c +#define UFS_PHY_ICE_CORE_CLK_CMD_RCGR 0x83074 +#define UFS_PHY_PHY_AUX_CLK_CMD_RCGR 0x830a8 +#define UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR 0x8308c + #define GCC_QUPV3_WRAP0_S0_CLK_ENA_BIT BIT(10) #define GCC_QUPV3_WRAP0_S1_CLK_ENA_BIT BIT(11) #define GCC_QUPV3_WRAP0_S2_CLK_ENA_BIT BIT(12) @@ -44,9 +49,35 @@ #define GCC_QUPV3_WRAP3_S0_CLK_ENA_BIT BIT(25) +/* UFS AXI clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_axi_clk_src[] = { + F(25000000, CFG_CLK_SRC_GPLL0_EVEN, 12, 0, 0), + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0, 2, 0, 0), + { } +}; + +/* UFS ICE CORE clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_ice_core_clk_src[] = { + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0, 2, 0, 0), + { } +}; + +/* UFS UNIPRO CORE clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_unipro_core_clk_src[] = { + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0, 2, 0, 0), + { } +}; + static ulong sa8775p_set_rate(struct clk *clk, ulong rate) { struct msm_clk_priv *priv = dev_get_priv(clk->dev); + const struct freq_tbl *freq; if (clk->id < priv->data->num_clks) debug("%s: %s, requested rate=%ld\n", __func__, @@ -63,6 +94,24 @@ static ulong sa8775p_set_rate(struct clk *clk, ulong rate) 5, 0, 0, CFG_CLK_SRC_GPLL0, 8); clk_rcg_set_rate(priv->base, USB3_PRIM_PHY_AUX_CMD_RCGR, 0, 0); return rate; + case GCC_UFS_PHY_AXI_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_axi_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_AXI_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_UNIPRO_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_unipro_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_ICE_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_ice_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_ICE_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_PHY_AUX_CLK: + clk_rcg_set_rate(priv->base, UFS_PHY_PHY_AUX_CLK_CMD_RCGR, 0, CFG_CLK_SRC_CXO); + return 19200000; default: return 0; } @@ -106,6 +155,20 @@ static const struct gate_clk sa8775p_clks[] = { /* QUP Wrapper 3 clocks */ GATE_CLK(GCC_QUPV3_WRAP3_S0_CLK, 0x4b000, GCC_QUPV3_WRAP3_S0_CLK_ENA_BIT), + + /* UFS PHY clocks */ + GATE_CLK(GCC_UFS_PHY_AXI_CLK, 0x83018, 1), + GATE_CLK(GCC_AGGRE_UFS_PHY_AXI_CLK, 0x830d4, 1), + GATE_CLK(GCC_UFS_PHY_AHB_CLK, 0x83020, 1), + GATE_CLK(GCC_UFS_PHY_UNIPRO_CORE_CLK, 0x83064, 1), + GATE_CLK(GCC_UFS_PHY_TX_SYMBOL_0_CLK, 0x83024, 1), + GATE_CLK(GCC_UFS_PHY_RX_SYMBOL_0_CLK, 0x83028, 1), + GATE_CLK(GCC_UFS_PHY_RX_SYMBOL_1_CLK, 0x830c0, 1), + GATE_CLK(GCC_UFS_PHY_PHY_AUX_CLK, 0x830a4, 1), + GATE_CLK(GCC_UFS_PHY_ICE_CORE_CLK, 0x8306c, 1), + + /* EDP reference clock (used by UFS PHY) */ + GATE_CLK(GCC_EDP_REF_CLKREF_EN, 0x97448, 1), }; static int sa8775p_enable(struct clk *clk) From d932606bae16b2090096f50d6a899d8a346cc763 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:29:43 +0530 Subject: [PATCH 16/35] clk: qcom: qcs615: Add UFS clock support Add UFS clock support for qcs615 including register definitions, rate configuration, and gate clocks. Signed-off-by: Balaji Selvanathan --- drivers/clk/qcom/clock-qcs615.c | 61 ++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/drivers/clk/qcom/clock-qcs615.c b/drivers/clk/qcom/clock-qcs615.c index 094155e20345..76f5f8191b5e 100644 --- a/drivers/clk/qcom/clock-qcs615.c +++ b/drivers/clk/qcom/clock-qcs615.c @@ -19,6 +19,11 @@ #define USB30_PRIM_MASTER_CLK_CMD_RCGR 0xf01c #define USB3_PRIM_PHY_AUX_CMD_RCGR 0xf060 +#define UFS_PHY_AXI_CLK_CMD_RCGR 0x77020 +#define UFS_PHY_ICE_CORE_CLK_CMD_RCGR 0x77048 +#define UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR 0x77060 +#define UFS_PHY_PHY_AUX_CLK_CMD_RCGR 0x7707c + #define SDCC1_APPS_CLK_CMD_RCGR 0x12028 #define SDCC2_APPS_CLK_CMD_RCGR 0x1400c @@ -61,6 +66,33 @@ static const struct freq_tbl ftbl_gcc_sdcc2_apps_clk_src[] = { #define GCC_QUPV3_WRAP1_S4_CLK_ENA_BIT BIT(26) #define GCC_QUPV3_WRAP1_S5_CLK_ENA_BIT BIT(27) +/* UFS PHY AXI clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_axi_clk_src[] = { + F(25000000, CFG_CLK_SRC_GPLL0_EVEN, 12, 0, 0), + F(50000000, CFG_CLK_SRC_GPLL0_EVEN, 6, 0, 0), + F(100000000, CFG_CLK_SRC_GPLL0, 6, 0, 0), + F(200000000, CFG_CLK_SRC_GPLL0, 3, 0, 0), + F(240000000, CFG_CLK_SRC_GPLL0, 2.5, 0, 0), + { } +}; + +/* UFS PHY ICE CORE clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_ice_core_clk_src[] = { + F(37500000, CFG_CLK_SRC_GPLL0_EVEN, 8, 0, 0), + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0, 2, 0, 0), + { } +}; + +/* UFS PHY UNIPRO CORE clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_unipro_core_clk_src[] = { + F(37500000, CFG_CLK_SRC_GPLL0_EVEN, 8, 0, 0), + F(75000000, CFG_CLK_SRC_GPLL0, 8, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + { } +}; + static ulong qcs615_set_rate(struct clk *clk, ulong rate) { struct msm_clk_priv *priv = dev_get_priv(clk->dev); @@ -91,6 +123,24 @@ static ulong qcs615_set_rate(struct clk *clk, ulong rate) clk_rcg_set_rate_mnd(priv->base, SDCC2_APPS_CLK_CMD_RCGR, freq->pre_div, freq->m, freq->n, freq->src, 8); return freq->freq; + case GCC_UFS_PHY_AXI_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_axi_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_AXI_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_UNIPRO_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_unipro_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_ICE_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_ice_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_ICE_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_PHY_AUX_CLK: + clk_rcg_set_rate(priv->base, UFS_PHY_PHY_AUX_CLK_CMD_RCGR, 0, CFG_CLK_SRC_CXO); + return 19200000; default: return 0; } @@ -125,7 +175,16 @@ static const struct gate_clk qcs615_clks[] = { GATE_CLK(GCC_SDCC1_APPS_CLK, 0x12004, BIT(0)), GATE_CLK(GCC_SDCC1_ICE_CORE_CLK, 0x1200c, BIT(0)), GATE_CLK(GCC_SDCC2_AHB_CLK, 0x14008, BIT(0)), - GATE_CLK(GCC_SDCC2_APPS_CLK, 0x14004, BIT(0)) + GATE_CLK(GCC_SDCC2_APPS_CLK, 0x14004, BIT(0)), + GATE_CLK(GCC_UFS_PHY_AXI_CLK, 0x77010, BIT(0)), + GATE_CLK(GCC_AGGRE_UFS_PHY_AXI_CLK, 0x770c0, BIT(0)), + GATE_CLK(GCC_UFS_PHY_AHB_CLK, 0x77014, BIT(0)), + GATE_CLK(GCC_UFS_PHY_UNIPRO_CORE_CLK, 0x77040, BIT(0)), + GATE_CLK(GCC_UFS_PHY_ICE_CORE_CLK, 0x77044, BIT(0)), + GATE_CLK(GCC_UFS_PHY_TX_SYMBOL_0_CLK, 0x77018, BIT(0)), + GATE_CLK(GCC_UFS_PHY_RX_SYMBOL_0_CLK, 0x7701c, BIT(0)), + GATE_CLK(GCC_UFS_PHY_PHY_AUX_CLK, 0x77078, BIT(0)), + GATE_CLK(GCC_UFS_MEM_CLKREF_CLK, 0x8c000, BIT(0)) }; static int qcs615_enable(struct clk *clk) From cf2ff637b43a880a27470bc8c9c5e8ed2cec9a27 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:30:36 +0530 Subject: [PATCH 17/35] clk: qcom: sc7280: Add UFS clock support Add UFS clock support for sc7280 including register definitions, rate configuration, and gate clocks. Signed-off-by: Balaji Selvanathan --- drivers/clk/qcom/clock-sc7280.c | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/drivers/clk/qcom/clock-sc7280.c b/drivers/clk/qcom/clock-sc7280.c index 01c8587ac398..91e3fcc27cb2 100644 --- a/drivers/clk/qcom/clock-sc7280.c +++ b/drivers/clk/qcom/clock-sc7280.c @@ -23,6 +23,10 @@ #define PCIE_1_AUX_CLK_CMD_RCGR 0x8d058 #define PCIE1_PHY_RCHNG_CMD_RCGR 0x8d03c #define PCIE_1_PIPE_CLK_PHY_MUX 0x8d054 +#define UFS_PHY_AXI_CLK_CMD_RCGR 0x77024 +#define UFS_PHY_ICE_CORE_CLK_CMD_RCGR 0x7706c +#define UFS_PHY_PHY_AUX_CLK_CMD_RCGR 0x770a0 +#define UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR 0x77084 static const struct freq_tbl ftbl_gcc_usb30_prim_master_clk_src[] = { F(66666667, CFG_CLK_SRC_GPLL0_EVEN, 4.5, 0, 0), @@ -54,6 +58,33 @@ static const struct freq_tbl ftbl_gcc_qupv3_wrap0_s2_clk_src[] = { { } }; +static const struct freq_tbl ftbl_gcc_ufs_phy_axi_clk_src[] = { + F(25000000, CFG_CLK_SRC_GPLL0_EVEN, 12, 0, 0), + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0_EVEN, 2, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_gcc_ufs_phy_ice_core_clk_src[] = { + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0_EVEN, 2, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_gcc_ufs_phy_phy_aux_clk_src[] = { + F(19200000, CFG_CLK_SRC_CXO, 1, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_gcc_ufs_phy_unipro_core_clk_src[] = { + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0_EVEN, 2, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 0, 0), + { } +}; + static ulong sc7280_set_rate(struct clk *clk, ulong rate) { struct msm_clk_priv *priv = dev_get_priv(clk->dev); @@ -103,6 +134,26 @@ static ulong sc7280_set_rate(struct clk *clk, ulong rate) case GCC_PCIE1_PHY_RCHNG_CLK: clk_rcg_set_rate(priv->base, PCIE1_PHY_RCHNG_CMD_RCGR, 5, CFG_CLK_SRC_GPLL0_EVEN); return 100000000; + case GCC_UFS_PHY_AXI_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_axi_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_AXI_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_ICE_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_ice_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_ICE_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_PHY_AUX_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_phy_aux_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_PHY_AUX_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_UNIPRO_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_unipro_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; default: return rate; } @@ -148,6 +199,7 @@ static const struct gate_clk sc7280_clks[] = { GATE_CLK(GCC_UFS_PHY_AXI_CLK, 0x77010, BIT(0)), GATE_CLK(GCC_AGGRE_UFS_PHY_AXI_CLK, 0x770cc, BIT(0)), GATE_CLK(GCC_UFS_PHY_AHB_CLK, 0x77018, BIT(0)), + GATE_CLK(GCC_UFS_PHY_ICE_CORE_CLK, 0x77064, BIT(0)), GATE_CLK(GCC_UFS_PHY_UNIPRO_CORE_CLK, 0x7705c, BIT(0)), GATE_CLK(GCC_UFS_PHY_PHY_AUX_CLK, 0x7709c, BIT(0)), GATE_CLK(GCC_UFS_PHY_TX_SYMBOL_0_CLK, 0x7701c, BIT(0)), From c4c14ab322532b36249ccd2d48f499d62a48c333 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:31:26 +0530 Subject: [PATCH 18/35] drivers: ufs: qcom: Initialize and enable clocks before hardware access Move UFS clock initialization and enabling before hardware setup to ensure clocks are running when accessing UFS registers. Previously, U-Boot depended on earlier bootloader stages to initialize UFS clocks. When these bootloaders failed to do so, UFS registers became inaccessible, causing initialization to fail. This change makes U-Boot initialize and enable UFS clocks early in the init sequence, removing the dependency on previous bootloaders. Signed-off-by: Balaji Selvanathan --- drivers/ufs/ufs-qcom.c | 53 ++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/drivers/ufs/ufs-qcom.c b/drivers/ufs/ufs-qcom.c index dc40ee62daf8..ae33f62fbee9 100644 --- a/drivers/ufs/ufs-qcom.c +++ b/drivers/ufs/ufs-qcom.c @@ -30,6 +30,7 @@ #define UFS_CPU_MAX_BANDWIDTH 819200 static void ufs_qcom_dev_ref_clk_ctrl(struct ufs_hba *hba, bool enable); +static u32 ufs_qcom_get_core_clk_unipro_max_freq(struct ufs_hba *hba); static int ufs_qcom_enable_clks(struct ufs_qcom_priv *priv) { @@ -47,17 +48,6 @@ static int ufs_qcom_enable_clks(struct ufs_qcom_priv *priv) return 0; } -static int ufs_qcom_init_clks(struct ufs_qcom_priv *priv) -{ - int err; - struct udevice *dev = priv->hba->dev; - - err = clk_get_bulk(dev, &priv->clks); - if (err) - return err; - - return 0; -} static int ufs_qcom_check_hibern8(struct ufs_hba *hba) { @@ -557,10 +547,45 @@ static void ufs_qcom_dev_ref_clk_ctrl(struct ufs_hba *hba, bool enable) static int ufs_qcom_init(struct ufs_hba *hba) { struct ufs_qcom_priv *priv = dev_get_priv(hba->dev); + struct udevice *dev = hba->dev; + struct clk clk; + u32 max_freq; + long rate; int err; priv->hba = hba; + /* Get maximum frequency for core_clk_unipro from device tree */ + max_freq = ufs_qcom_get_core_clk_unipro_max_freq(hba); + + /* Get and configure core_clk_unipro */ + err = clk_get_by_name(dev, "core_clk_unipro", &clk); + if (err) { + dev_err(dev, "Failed to get core_clk_unipro: %d\n", err); + return err; + } + + rate = clk_set_rate(&clk, max_freq); + if (rate < 0) { + dev_err(dev, "Failed to set core_clk_unipro rate to %u Hz: %ld\n", + max_freq, rate); + } + + /* Get all clocks */ + err = clk_get_bulk(dev, &priv->clks); + if (err) { + dev_err(dev, "clk_get_bulk failed: %d\n", err); + return err; + } + + /* Enable clocks */ + err = ufs_qcom_enable_clks(priv); + if (err) { + dev_err(dev, "failed to enable clocks: %d\n", err); + clk_release_bulk(&priv->clks); + return err; + } + /* setup clocks */ ufs_qcom_setup_clocks(hba, true, PRE_CHANGE); @@ -579,12 +604,6 @@ static int ufs_qcom_init(struct ufs_hba *hba) priv->hw_ver.minor, priv->hw_ver.step); - err = ufs_qcom_init_clks(priv); - if (err) { - dev_err(hba->dev, "failed to initialize clocks, err:%d\n", err); - return err; - } - ufs_qcom_advertise_quirks(hba); ufs_qcom_setup_clocks(hba, true, POST_CHANGE); From b64eed1e7c646f2ba704daa0845be65df3a11ac2 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 10:02:51 +0530 Subject: [PATCH 19/35] ufs: qcom: Remove redundant POST_CHANGE clock setup call The ufs_qcom_init() function was calling ufs_qcom_setup_clocks() with POST_CHANGE twice. The first call after setting PA_TXHSADAPTTYPE correctly enables the device reference clock. The second call after ufs_qcom_advertise_quirks() is redundant as the clock is already enabled. Signed-off-by: Balaji Selvanathan --- drivers/ufs/ufs-qcom.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/ufs/ufs-qcom.c b/drivers/ufs/ufs-qcom.c index ae33f62fbee9..f5f5a6eb110c 100644 --- a/drivers/ufs/ufs-qcom.c +++ b/drivers/ufs/ufs-qcom.c @@ -605,7 +605,6 @@ static int ufs_qcom_init(struct ufs_hba *hba) priv->hw_ver.step); ufs_qcom_advertise_quirks(hba); - ufs_qcom_setup_clocks(hba, true, POST_CHANGE); return 0; } From e2036d7effdcf7d3ff8d11b09fb014535f9c5f6e Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 24 Apr 2026 12:00:17 +0530 Subject: [PATCH 20/35] fastboot: block: Add device selection syntax Implement device selection syntax allowing users to specify the target block device using "N:partition" format, where N is the device number. When no device is specified, the default from CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID is used. Modify fastboot_block_get_part_info() to use the new parsing function, enabling operations like "fastboot flash 0:boot boot.img" to write to specific devices while maintaining backward compatibility with the existing "fastboot flash boot boot.img" syntax. Example usage: fastboot flash 0:boot boot.img # Flash to device 0 fastboot flash 1:system system.img # Flash to device 1 fastboot flash boot boot.img # Use default device Signed-off-by: Balaji Selvanathan --- drivers/fastboot/fb_block.c | 53 ++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/drivers/fastboot/fb_block.c b/drivers/fastboot/fb_block.c index 51d1abb18c77..061ffd930a56 100644 --- a/drivers/fastboot/fb_block.c +++ b/drivers/fastboot/fb_block.c @@ -11,6 +11,7 @@ #include #include #include +#include /** * FASTBOOT_MAX_BLOCKS_ERASE - maximum blocks to erase per derase call @@ -124,6 +125,44 @@ static lbaint_t fb_block_sparse_reserve(struct sparse_storage *info, return blkcnt; } +/** + * parse_device_partition() - Parse and validate device:partition format + * @part_name: Input string in format "N:partition" or "partition" + * @device: Output device number + * @partition_name: Output partition name pointer (can be NULL) + * + * Parses the input string to extract device number and partition name. + * If no device is specified, uses the default from config. + * Returns: 0 on success, -EINVAL if format is invalid + */ +static int parse_device_partition(const char *part_name, int *device, + const char **partition_name) +{ + const char *colon_pos; + + *device = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, + CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID, -1); + + /* Check for colon in partition name */ + colon_pos = strchr(part_name, ':'); + + /* Reject invalid format like ":partition" */ + if (colon_pos && colon_pos == part_name) + return -EINVAL; + + /* Override if device:partition format detected */ + if (colon_pos && colon_pos > part_name) { + *device = simple_strtoul(part_name, NULL, 10); + if (partition_name) + *partition_name = colon_pos + 1; + } else { + if (partition_name) + *partition_name = part_name; + } + + return 0; +} + int fastboot_block_get_part_info(const char *part_name, struct blk_desc **dev_desc, struct disk_partition *part_info, @@ -133,25 +172,31 @@ int fastboot_block_get_part_info(const char *part_name, const char *interface = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, NULL); - const int device = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, - CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID, -1); + int device; + const char *partition_name; if (!part_name || !strcmp(part_name, "")) { fastboot_fail("partition not given", response); return -ENOENT; } + if (!interface || !strcmp(interface, "")) { fastboot_fail("block interface isn't provided", response); return -EINVAL; } + if (parse_device_partition(part_name, &device, &partition_name) < 0) { + fastboot_fail("invalid partition name format", response); + return -EINVAL; + } + *dev_desc = blk_get_dev(interface, device); - if (!dev_desc) { + if (!*dev_desc) { fastboot_fail("no such device", response); return -ENODEV; } - ret = part_get_info_by_name(*dev_desc, part_name, part_info); + ret = part_get_info_by_name(*dev_desc, partition_name, part_info); if (ret < 0) fastboot_fail("failed to get partition info", response); From 42bc31b47a1c413044cc4dc1e48abac28f63891b Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 24 Apr 2026 12:03:38 +0530 Subject: [PATCH 21/35] fastboot: Add GPT/MBR partition table flashing helper functions Add fastboot_flash_gpt_partition_table() and fastboot_flash_mbr_partition_table() helper functions that handle flashing of GPT and MBR partition tables to block devices. The MMC backend now uses these helper functions for GPT and MBR operations, simplifying the code while maintaining the same functionality. Signed-off-by: Balaji Selvanathan --- drivers/fastboot/fb_common.c | 94 ++++++++++++++++++++++++++++++++++++ drivers/fastboot/fb_mmc.c | 49 +++---------------- include/fastboot-internal.h | 30 ++++++++++++ 3 files changed, 131 insertions(+), 42 deletions(-) diff --git a/drivers/fastboot/fb_common.c b/drivers/fastboot/fb_common.c index 9c52e0045881..2ff6e6567cf0 100644 --- a/drivers/fastboot/fb_common.c +++ b/drivers/fastboot/fb_common.c @@ -245,3 +245,97 @@ void fastboot_init(void *buf_addr, u32 buf_size) fastboot_set_progress_callback(NULL); } + +#if CONFIG_IS_ENABLED(EFI_PARTITION) +/** + * fastboot_flash_gpt_partition_table() - Flash GPT partition table + * @interface: Block interface name (e.g., "mmc", "scsi") + * @device: Device number + * @download_buffer: Buffer containing GPT data + * @response: Fastboot response buffer + */ +void fastboot_flash_gpt_partition_table(const char *interface, + int device, + void *download_buffer, + char *response) +{ + struct blk_desc *dev_desc; + + if (!interface || !strcmp(interface, "")) { + fastboot_fail("block interface isn't provided", response); + return; + } + + dev_desc = blk_get_dev(interface, device); + if (!dev_desc) { + fastboot_fail("no such device", response); + return; + } + + printf("%s: updating MBR, Primary and Backup GPT(s) on %s device %d\n", + __func__, interface, dev_desc->devnum); + + if (is_valid_gpt_buf(dev_desc, download_buffer)) { + printf("%s: invalid GPT - refusing to write to flash\n", __func__); + fastboot_fail("invalid GPT partition", response); + return; + } + + if (write_mbr_and_gpt_partitions(dev_desc, download_buffer)) { + printf("%s: writing GPT partitions failed\n", __func__); + fastboot_fail("writing GPT partitions failed", response); + return; + } + + part_init(dev_desc); + printf("........ success\n"); + fastboot_okay(NULL, response); +} +#endif + +#if CONFIG_IS_ENABLED(DOS_PARTITION) +/** + * fastboot_flash_mbr_partition_table() - Flash MBR partition table + * @interface: Block interface name (e.g., "mmc", "scsi") + * @device: Device number + * @download_buffer: Buffer containing MBR data + * @response: Fastboot response buffer + */ +void fastboot_flash_mbr_partition_table(const char *interface, + int device, + void *download_buffer, + char *response) +{ + struct blk_desc *dev_desc; + + if (!interface || !strcmp(interface, "")) { + fastboot_fail("block interface isn't provided", response); + return; + } + + dev_desc = blk_get_dev(interface, device); + if (!dev_desc) { + fastboot_fail("no such device", response); + return; + } + + printf("%s: updating MBR on %s device %d\n", __func__, interface, + dev_desc->devnum); + + if (is_valid_dos_buf(download_buffer)) { + printf("%s: invalid MBR - refusing to write to flash\n", __func__); + fastboot_fail("invalid MBR partition", response); + return; + } + + if (write_mbr_sector(dev_desc, download_buffer)) { + printf("%s: writing MBR partition failed\n", __func__); + fastboot_fail("writing MBR partition failed", response); + return; + } + + part_init(dev_desc); + printf("........ success\n"); + fastboot_okay(NULL, response); +} +#endif diff --git a/drivers/fastboot/fb_mmc.c b/drivers/fastboot/fb_mmc.c index 11d9c8e84602..c85853915c88 100644 --- a/drivers/fastboot/fb_mmc.c +++ b/drivers/fastboot/fb_mmc.c @@ -343,7 +343,7 @@ int fastboot_mmc_get_part_info(const char *part_name, return ret; } -static struct blk_desc *fastboot_mmc_get_dev(char *response) +static struct blk_desc __maybe_unused *fastboot_mmc_get_dev(char *response) { struct blk_desc *ret = blk_get_dev("mmc", CONFIG_FASTBOOT_FLASH_MMC_DEV); @@ -389,53 +389,18 @@ void fastboot_mmc_flash_write(const char *cmd, void *download_buffer, #if CONFIG_IS_ENABLED(EFI_PARTITION) if (strcmp(cmd, CONFIG_FASTBOOT_GPT_NAME) == 0) { - dev_desc = fastboot_mmc_get_dev(response); - if (!dev_desc) - return; - - printf("%s: updating MBR, Primary and Backup GPT(s)\n", - __func__); - if (is_valid_gpt_buf(dev_desc, download_buffer)) { - printf("%s: invalid GPT - refusing to write to flash\n", - __func__); - fastboot_fail("invalid GPT partition", response); - return; - } - if (write_mbr_and_gpt_partitions(dev_desc, download_buffer)) { - printf("%s: writing GPT partitions failed\n", __func__); - fastboot_fail("writing GPT partitions failed", - response); - return; - } - part_init(dev_desc); - printf("........ success\n"); - fastboot_okay(NULL, response); + fastboot_flash_gpt_partition_table("mmc", + CONFIG_FASTBOOT_FLASH_MMC_DEV, + download_buffer, response); return; } #endif #if CONFIG_IS_ENABLED(DOS_PARTITION) if (strcmp(cmd, CONFIG_FASTBOOT_MBR_NAME) == 0) { - dev_desc = fastboot_mmc_get_dev(response); - if (!dev_desc) - return; - - printf("%s: updating MBR\n", __func__); - if (is_valid_dos_buf(download_buffer)) { - printf("%s: invalid MBR - refusing to write to flash\n", - __func__); - fastboot_fail("invalid MBR partition", response); - return; - } - if (write_mbr_sector(dev_desc, download_buffer)) { - printf("%s: writing MBR partition failed\n", __func__); - fastboot_fail("writing MBR partition failed", - response); - return; - } - part_init(dev_desc); - printf("........ success\n"); - fastboot_okay(NULL, response); + fastboot_flash_mbr_partition_table("mmc", + CONFIG_FASTBOOT_FLASH_MMC_DEV, + download_buffer, response); return; } #endif diff --git a/include/fastboot-internal.h b/include/fastboot-internal.h index 610d4f914140..35683cdb642d 100644 --- a/include/fastboot-internal.h +++ b/include/fastboot-internal.h @@ -40,4 +40,34 @@ void fastboot_getvar_all(char *response); */ void fastboot_getvar(char *cmd_parameter, char *response); +#if CONFIG_IS_ENABLED(EFI_PARTITION) +/** + * fastboot_flash_gpt_partition_table() - Flash GPT partition table + * + * @interface: Block interface name (e.g., "mmc", "scsi") + * @device: Device number + * @download_buffer: Buffer containing GPT data + * @response: Pointer to fastboot response buffer + */ +void fastboot_flash_gpt_partition_table(const char *interface, + int device, + void *download_buffer, + char *response); +#endif + +#if CONFIG_IS_ENABLED(DOS_PARTITION) +/** + * fastboot_flash_mbr_partition_table() - Flash MBR partition table + * + * @interface: Block interface name (e.g., "mmc", "scsi") + * @device: Device number + * @download_buffer: Buffer containing MBR data + * @response: Pointer to fastboot response buffer + */ +void fastboot_flash_mbr_partition_table(const char *interface, + int device, + void *download_buffer, + char *response); +#endif + #endif From 1192811bef08809c73909d6aaa3fd9cf3ff327ec Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 24 Apr 2026 12:07:04 +0530 Subject: [PATCH 22/35] fastboot: block: Add GPT/MBR partition table flashing support Add support for flashing GPT and MBR partition tables to the fastboot block backend. This enables operations like "fastboot flash gpt gpt.img" and "fastboot flash mbr mbr.img" for block devices. The implementation validates partition table names and rejects invalid input formats such as ":gpt" or ":mbr" where the device prefix is missing. Valid formats include "gpt", "mbr", "0:gpt", and "1:mbr". Update Kconfig dependencies to allow FASTBOOT_GPT_NAME and FASTBOOT_MBR_NAME to work with both MMC and block backends.` Signed-off-by: Balaji Selvanathan --- drivers/fastboot/Kconfig | 4 +-- drivers/fastboot/fb_block.c | 49 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/drivers/fastboot/Kconfig b/drivers/fastboot/Kconfig index 90212fcf9efd..a4539844d898 100644 --- a/drivers/fastboot/Kconfig +++ b/drivers/fastboot/Kconfig @@ -227,7 +227,7 @@ config FASTBOOT_FLASH_BLOCK_DEVICE_ID config FASTBOOT_GPT_NAME string "Target name for updating GPT" - depends on FASTBOOT_FLASH_MMC && EFI_PARTITION + depends on (FASTBOOT_FLASH_MMC || FASTBOOT_FLASH_BLOCK) && EFI_PARTITION default "gpt" help The fastboot "flash" command supports writing the downloaded @@ -240,7 +240,7 @@ config FASTBOOT_GPT_NAME config FASTBOOT_MBR_NAME string "Target name for updating MBR" - depends on FASTBOOT_FLASH_MMC && DOS_PARTITION + depends on (FASTBOOT_FLASH_MMC || FASTBOOT_FLASH_BLOCK) && DOS_PARTITION default "mbr" help The fastboot "flash" command allows to write the downloaded image diff --git a/drivers/fastboot/fb_block.c b/drivers/fastboot/fb_block.c index 061ffd930a56..a8c74e35d0d1 100644 --- a/drivers/fastboot/fb_block.c +++ b/drivers/fastboot/fb_block.c @@ -163,6 +163,27 @@ static int parse_device_partition(const char *part_name, int *device, return 0; } +/** + * is_partition_table_name() - Check if name matches partition table target + * @part_name: Partition name to check + * @table_name: Config name for partition table (e.g., "gpt", "mbr") + * + * Returns: true if part_name matches table_name (with or without device prefix) + */ +static bool is_partition_table_name(const char *part_name, const char *table_name) +{ + const char *colon_pos; + + if (strcmp(part_name, table_name) == 0) + return true; + + colon_pos = strchr(part_name, ':'); + if (colon_pos && colon_pos > part_name && strcmp(colon_pos + 1, table_name) == 0) + return true; + + return false; +} + int fastboot_block_get_part_info(const char *part_name, struct blk_desc **dev_desc, struct disk_partition *part_info, @@ -361,6 +382,34 @@ void fastboot_block_flash_write(const char *part_name, void *download_buffer, struct blk_desc *dev_desc; struct disk_partition part_info; +#if CONFIG_IS_ENABLED(EFI_PARTITION) + if (is_partition_table_name(part_name, CONFIG_FASTBOOT_GPT_NAME)) { + int device; + const char *interface = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, + CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, + NULL); + + parse_device_partition(part_name, &device, NULL); + fastboot_flash_gpt_partition_table(interface, device, + download_buffer, response); + return; + } +#endif + +#if CONFIG_IS_ENABLED(DOS_PARTITION) + if (is_partition_table_name(part_name, CONFIG_FASTBOOT_MBR_NAME)) { + int device; + const char *interface = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, + CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, + NULL); + + parse_device_partition(part_name, &device, NULL); + fastboot_flash_mbr_partition_table(interface, device, + download_buffer, response); + return; + } +#endif + if (fastboot_block_get_part_info(part_name, &dev_desc, &part_info, response) < 0) return; From afd09646efd2b1e71c047b0750a12c2925a3b2d3 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 10 Apr 2026 10:02:50 +0530 Subject: [PATCH 23/35] doc: fastboot: Document block device selection syntax Add documentation for the device selection syntax in fastboot block device operations. Users can now specify target devices using "N:partition" format where N is the device number. Document usage examples for regular partition operations like "fastboot flash 0:boot boot.img" and partition table operations like "fastboot flash 1:gpt gpt.img". When no device number is specified, the default from CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID is used. Reviewed-by: Mattijs Korpershoek Signed-off-by: Balaji Selvanathan --- doc/android/fastboot.rst | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/doc/android/fastboot.rst b/doc/android/fastboot.rst index 19e2ee9d4074..ce3a030e701b 100644 --- a/doc/android/fastboot.rst +++ b/doc/android/fastboot.rst @@ -147,6 +147,38 @@ The Fastboot implementation in U-Boot allows to write images into disk partitions. Target partitions are referred on the host computer by their names. +Device Selection for Block Devices +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When using ``FASTBOOT_FLASH_BLOCK``, you can specify which block device to +target by prefixing the partition name with the device number and a colon. + +Syntax:: + + : + +Examples:: + + fastboot flash 0:boot boot.img # Flash to device 0, partition "boot" + fastboot flash 1:system system.img # Flash to device 1, partition "system" + fastboot flash 0:gpt gpt.img # Write GPT to device 0 + fastboot flash 1:mbr mbr.img # Write MBR to device 1 + +If no device number is specified, the default device from +``CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID`` is used:: + + fastboot flash boot boot.img # Uses default device + +This syntax is supported for: + +* Regular partition flashing +* GPT partition table updates (``gpt`` or ``N:gpt``) +* MBR partition table updates (``mbr`` or ``N:mbr``) +* Partition erasing operations + +Partition Name Formats +^^^^^^^^^^^^^^^^^^^^^^ + For GPT/EFI the respective partition name is used. For MBR the partitions are referred by generic names according to the @@ -186,6 +218,15 @@ configuration options: CONFIG_FASTBOOT_GPT_NAME CONFIG_FASTBOOT_MBR_NAME +When using block devices (``FASTBOOT_FLASH_BLOCK``), you can specify the target +device by prefixing with the device number:: + + fastboot flash 0:gpt gpt.img # Write GPT to device 0 + fastboot flash 1:mbr mbr.img # Write MBR to device 1 + +If no device number is specified, the default device from +``CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID`` is used. + In Action --------- From 7a82a69bc2e56bdf0df04378b2b6902f995790a6 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 17 Apr 2026 15:09:24 +0530 Subject: [PATCH 24/35] configs: Rename qcs9100 defconfig to lemans defconfig Rename qcom_qcs9100_defconfig to qcom_lemans_defconfig and update the defconfig to select lemans-evk DTS instead of qcs9100-ride-r3. Signed-off-by: Aswin Murugan --- configs/{qcom_qcs9100_defconfig => qcom_lemans_defconfig} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename configs/{qcom_qcs9100_defconfig => qcom_lemans_defconfig} (91%) diff --git a/configs/qcom_qcs9100_defconfig b/configs/qcom_lemans_defconfig similarity index 91% rename from configs/qcom_qcs9100_defconfig rename to configs/qcom_lemans_defconfig index 082106157bbc..ad40d5206c04 100644 --- a/configs/qcom_qcs9100_defconfig +++ b/configs/qcom_lemans_defconfig @@ -9,7 +9,7 @@ CONFIG_TEXT_BASE=0xaf000000 CONFIG_REMAKE_ELF=y CONFIG_FASTBOOT_BUF_ADDR=0xdb300000 -CONFIG_DEFAULT_DEVICE_TREE="qcom/qcs9100-ride-r3" +CONFIG_DEFAULT_DEVICE_TREE="qcom/lemans-evk" CONFIG_ENV_IS_IN_SCSI=y CONFIG_ENV_SCSI_PART_UUID="71cb9cd0-acf1-b6cb-ad91-be9572fe11a9" # CONFIG_ENV_IS_DEFAULT is not set From b3643fbbaf2da90d5328a9fe7948528c0839d0a5 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 5 Feb 2026 14:42:20 +0530 Subject: [PATCH 25/35] qcom_defconfig: Switch Qualcomm fastboot flash from MMC to block Replace MMC-specific fastboot flash configuration with generic block device support for Qualcomm platforms. This change switches from MMC device 0 to SCSI interface with device ID 4, enabling fastboot operations on UFS storage instead of eMMC. Signed-off-by: Balaji Selvanathan --- configs/qcom_defconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/configs/qcom_defconfig b/configs/qcom_defconfig index 707ddd493208..e7dcb71672b2 100644 --- a/configs/qcom_defconfig +++ b/configs/qcom_defconfig @@ -79,8 +79,9 @@ CONFIG_SYS_DFU_DATA_BUF_SIZE=0x200000 CONFIG_USB_FUNCTION_FASTBOOT=y CONFIG_FASTBOOT_BUF_ADDR=0x0 CONFIG_FASTBOOT_FLASH=y -CONFIG_FASTBOOT_FLASH_MMC_DEV=0 -CONFIG_FASTBOOT_MMC_USER_SUPPORT=y +CONFIG_FASTBOOT_FLASH_BLOCK=y +CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME="scsi" +CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID=4 CONFIG_MSM_GPIO=y CONFIG_QCOM_PMIC_GPIO=y CONFIG_DM_I2C=y From 1836601553ae4958a3e78641225af452e5969f79 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 8 Jan 2026 10:35:30 +0530 Subject: [PATCH 26/35] disk: Add partition lookup by type GUID functionality Introduce part_get_info_by_type_guid() function to enable partition lookup using partition type GUID. This complements the existing UUID lookup functionality and provides more flexible partition discovery mechanisms. Reviewed-by: Simon Glass Signed-off-by: Balaji Selvanathan --- disk/part.c | 37 +++++++++++++++++++++++++++++++++++++ include/part.h | 21 +++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/disk/part.c b/disk/part.c index 4923dc44593c..4cb3204ac6e0 100644 --- a/disk/part.c +++ b/disk/part.c @@ -731,6 +731,43 @@ int part_get_info_by_uuid(struct blk_desc *desc, const char *uuid, return -ENOENT; } +int part_get_info_by_type_guid(struct blk_desc *desc, const char *type_guid, + struct disk_partition *info) +{ + struct part_driver *part_drv; + int ret; + int i; + + if (!CONFIG_IS_ENABLED(PARTITION_TYPE_GUID)) + return -ENOENT; + + part_drv = part_driver_lookup_type(desc); + if (!part_drv) + return -ENOENT; + + for (i = 1; i <= part_drv->max_entries; i++) { + ret = part_driver_get_info(part_drv, desc, i, info); + if (ret) { + /* -ENOSYS means no ->get_info method. */ + if (ret == -ENOSYS) + return ret; + /* + * Partition with this index can't be obtained, but + * further partitions might be, so keep checking. + */ + continue; + } + + if (!strncasecmp(type_guid, disk_partition_type_guid(info), + UUID_STR_LEN)) { + /* matched */ + return i; + } + } + + return -ENOENT; +} + /** * Get partition info from device number and partition name. * diff --git a/include/part.h b/include/part.h index 15daacd7faaa..32614bd085bc 100644 --- a/include/part.h +++ b/include/part.h @@ -327,6 +327,20 @@ int part_get_info_by_name(struct blk_desc *desc, const char *name, int part_get_info_by_uuid(struct blk_desc *desc, const char *uuid, struct disk_partition *info); +/** + * part_get_info_by_type_guid() - Search for a partition by type GUID + * among all available registered partitions + * + * @desc: block device descriptor + * @type_guid: the specified partition type GUID + * @info: the disk partition info + * + * Return: the partition number on match (starting on 1), -ENOENT on no match, + * otherwise error + */ +int part_get_info_by_type_guid(struct blk_desc *desc, const char *type_guid, + struct disk_partition *info); + /** * part_get_info_by_dev_and_name_or_num() - Get partition info from dev number * and part name, or dev number and @@ -404,6 +418,13 @@ static inline int part_get_info_by_uuid(struct blk_desc *desc, const char *uuid, return -ENOENT; } +static inline int part_get_info_by_type_guid(struct blk_desc *desc, + const char *type_guid, + struct disk_partition *info) +{ + return -ENOENT; +} + static inline int part_get_info_by_dev_and_name_or_num(const char *dev_iface, const char *dev_part_str, From 56e13e93e3a16cc215589a99c7670208e6b2a216 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 20:57:44 +0530 Subject: [PATCH 27/35] scsi: Add partition lookup by type GUID for SCSI devices Introduce scsi_get_blk_by_type_guid() function to enable SCSI partition discovery using partition type GUID. This function scans all available SCSI devices and searches for a partition matching the specified type GUID. Reviewed-by: Simon Glass Signed-off-by: Balaji Selvanathan --- drivers/scsi/scsi-uclass.c | 23 +++++++++++++++++++++++ include/scsi.h | 11 +++++++++++ 2 files changed, 34 insertions(+) diff --git a/drivers/scsi/scsi-uclass.c b/drivers/scsi/scsi-uclass.c index 39b4c7476d45..cea6d771f7c0 100644 --- a/drivers/scsi/scsi-uclass.c +++ b/drivers/scsi/scsi-uclass.c @@ -47,6 +47,29 @@ int scsi_get_blk_by_uuid(const char *uuid, return -ENODEV; } +int scsi_get_blk_by_type_guid(const char *type_guid, + struct blk_desc **blk_desc_ptr, + struct disk_partition *part_info_ptr) +{ + struct blk_desc *blk; + int i, ret, max; + + max = blk_find_max_devnum(UCLASS_SCSI) + 1; + for (i = 0; i < max; i++) { + ret = blk_get_desc(UCLASS_SCSI, i, &blk); + if (ret) + continue; + + ret = part_get_info_by_type_guid(blk, type_guid, part_info_ptr); + if (ret > 0) { + *blk_desc_ptr = blk; + return 0; + } + } + + return -ENODEV; +} + int scsi_bus_reset(struct udevice *dev) { struct scsi_ops *ops = scsi_get_ops(dev); diff --git a/include/scsi.h b/include/scsi.h index 2520a8b8fe63..83aaf0a70f63 100644 --- a/include/scsi.h +++ b/include/scsi.h @@ -366,6 +366,17 @@ int scsi_scan_dev(struct udevice *dev, bool verbose); int scsi_get_blk_by_uuid(const char *uuid, struct blk_desc **blk_desc_ptr, struct disk_partition *part_info_ptr); +/** + * scsi_get_blk_by_type_guid() - Provides SCSI partition information by type GUID. + * + * @type_guid: Type GUID of the partition for fetching its info + * @blk_desc_ptr: Provides the blk descriptor + * @part_info_ptr: Provides partition info + * Return: 0 if OK, -ve on error + */ +int scsi_get_blk_by_type_guid(const char *type_guid, struct blk_desc **blk_desc_ptr, + struct disk_partition *part_info_ptr); + #define SCSI_IDENTIFY 0xC0 /* not used */ /* Hardware errors */ From 2096269127fbfa27ad0a3c5fcce1a4d49ec0183c Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 20:59:29 +0530 Subject: [PATCH 28/35] scsi: Optimize scsi_get_blk_by_uuid() loop iteration Compute blk_find_max_devnum(UCLASS_SCSI) only once instead of on every loop iteration for better performance. Signed-off-by: Balaji Selvanathan --- drivers/scsi/scsi-uclass.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/scsi-uclass.c b/drivers/scsi/scsi-uclass.c index cea6d771f7c0..031dc01b9de6 100644 --- a/drivers/scsi/scsi-uclass.c +++ b/drivers/scsi/scsi-uclass.c @@ -30,9 +30,10 @@ int scsi_get_blk_by_uuid(const char *uuid, struct disk_partition *part_info_ptr) { struct blk_desc *blk; - int i, ret; + int i, ret, max; - for (i = 0; i < blk_find_max_devnum(UCLASS_SCSI) + 1; i++) { + max = blk_find_max_devnum(UCLASS_SCSI) + 1; + for (i = 0; i < max; i++) { ret = blk_get_desc(UCLASS_SCSI, i, &blk); if (ret) continue; From db70b2d7b854e025398266ac5220490ee6d2dd78 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 21:30:26 +0530 Subject: [PATCH 29/35] env: scsi: Fix ENV_SCSI_HW_PARTITION default value type Change the default value from integer 0 to string "0" to match the string type of the configuration option. Signed-off-by: Balaji Selvanathan --- env/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env/Kconfig b/env/Kconfig index 7abd82ab6f38..71a27894f90a 100644 --- a/env/Kconfig +++ b/env/Kconfig @@ -783,7 +783,7 @@ config ENV_MMC_USE_DT config ENV_SCSI_HW_PARTITION string "SCSI hardware partition number" depends on ENV_IS_IN_SCSI - default 0 + default "0" help SCSI hardware partition device number on the platform where the environment is stored. Note that this is not related to any software From bc8bb099b736879426e51d190d0a5ccf06fc170b Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 21:40:57 +0530 Subject: [PATCH 30/35] env: scsi: Add partition type GUID support and choice-based selection Add support for locating SCSI environment partition using GPT type GUID. Introduce a Kconfig choice statement to select between three mutually exclusive partition lookup methods: UUID-based (default), type GUID-based, and hardware partition number. Reorganize existing configs to depend on their respective choice options. Update ENV_IS_IN_SCSI help text to document the new configuration structure. Signed-off-by: Balaji Selvanathan --- env/Kconfig | 68 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/env/Kconfig b/env/Kconfig index 71a27894f90a..f59b2acb7e6c 100644 --- a/env/Kconfig +++ b/env/Kconfig @@ -299,16 +299,20 @@ config ENV_IS_IN_SCSI The size of the partition where the environment is stored in bytes. Must be a multiple of the partition block size. - - CONFIG_ENV_SCSI_HW_PARTITION: + The partition selection method is configured via a choice statement: - Specifies which SCSI partition the environment is stored in. If not - set, defaults to partition 0, the user area. Common values might be - 1 (first SCSI boot partition), 2 (second SCSI boot partition). Ignored - if CONFIG_ENV_SCSI_PART_UUID is set to non-empty string. + - ENV_SCSI_PART_USE_UUID: Use the partition's unique UUID to identify + the SCSI partition for environment storage. - - CONFIG_ENV_SCSI_PART_UUID: + - ENV_SCSI_PART_USE_TYPE_GUID: Use the partition type GUID to identify + the SCSI partition for environment storage. The first partition + matching the specified type GUID will be used. - UUID of the SCSI partition where the environment is stored. + - ENV_SCSI_PART_USE_HW: Use the hardware device number to identify + the SCSI device for environment storage. Specifies which SCSI + partition the environment is stored in. If not set, defaults to + partition 0, the user area. Common values might be 1 (first SCSI + boot partition), 2 (second SCSI boot partition). config ENV_RANGE @@ -780,9 +784,51 @@ config ENV_MMC_USE_DT The 2 defines CONFIG_ENV_OFFSET, CONFIG_ENV_OFFSET_REDUND are not used as fallback. +choice + prompt "SCSI partition selection method" + depends on ENV_IS_IN_SCSI + default ENV_SCSI_PART_USE_UUID + help + Select the method to identify the SCSI partition for environment storage. + +config ENV_SCSI_PART_USE_UUID + bool "Use partition UUID" + help + Use the partition's unique UUID to identify the SCSI partition + for environment storage. + +config ENV_SCSI_PART_USE_TYPE_GUID + bool "Use partition type GUID" + select PARTITION_TYPE_GUID + help + Use the partition type GUID to identify the SCSI partition + for environment storage. The first partition matching the + specified type GUID will be used. + +config ENV_SCSI_PART_USE_HW + bool "Use hardware partition number" + help + Use the hardware device number to identify the SCSI device + for environment storage. + +endchoice + +config ENV_SCSI_PART_UUID + string "SCSI partition UUID for saving environment" + depends on ENV_SCSI_PART_USE_UUID + help + UUID of the SCSI partition that you want to store the environment in. + +config ENV_SCSI_PART_TYPE_GUID + string "SCSI partition type GUID for saving environment" + depends on ENV_SCSI_PART_USE_TYPE_GUID + help + Type GUID of the SCSI partition to store the environment in. + Uses the first partition matching this type GUID. + config ENV_SCSI_HW_PARTITION string "SCSI hardware partition number" - depends on ENV_IS_IN_SCSI + depends on ENV_SCSI_PART_USE_HW default "0" help SCSI hardware partition device number on the platform where the @@ -791,12 +837,6 @@ config ENV_SCSI_HW_PARTITION partition 0 or the first boot partition, which is 1 or some other defined partition. -config ENV_SCSI_PART_UUID - string "SCSI partition UUID for saving environment" - depends on ENV_IS_IN_SCSI - help - UUID of the SCSI partition that you want to store the environment in. - config ENV_USE_DEFAULT_ENV_TEXT_FILE bool "Create default environment from file" depends on !COMPILE_TEST From e386ef1a1cc8da146fcc7dc0e5a1f3860c4a9a84 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 21:47:10 +0530 Subject: [PATCH 31/35] env: scsi: Implement partition type GUID lookup Update env/scsi.c to support the new partition selection methods introduced in the Kconfig. Replace runtime string checks with compile-time preprocessor conditionals. Implement support for all three partition selection methods: - TYPE_GUID: Uses scsi_get_blk_by_type_guid() - UUID: Uses scsi_get_blk_by_uuid() - HW: Uses blk_get_device_part_str() Signed-off-by: Balaji Selvanathan --- env/scsi.c | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/env/scsi.c b/env/scsi.c index 91a6c430302c..b170f4ee0c7f 100644 --- a/env/scsi.c +++ b/env/scsi.c @@ -41,14 +41,17 @@ static inline struct env_scsi_info *env_scsi_get_part(void) is_scsi_scanned = true; } - if (CONFIG_ENV_SCSI_PART_UUID[0] == '\0') { - if (blk_get_device_part_str("scsi", CONFIG_ENV_SCSI_HW_PARTITION, - &ep->blk, &ep->part, true)) - return NULL; - } else { - if (scsi_get_blk_by_uuid(CONFIG_ENV_SCSI_PART_UUID, &ep->blk, &ep->part)) - return NULL; - } +#if defined(CONFIG_ENV_SCSI_PART_USE_TYPE_GUID) + if (scsi_get_blk_by_type_guid(CONFIG_ENV_SCSI_PART_TYPE_GUID, &ep->blk, &ep->part)) + return NULL; +#elif defined(CONFIG_ENV_SCSI_PART_USE_UUID) + if (scsi_get_blk_by_uuid(CONFIG_ENV_SCSI_PART_UUID, &ep->blk, &ep->part)) + return NULL; +#elif defined(CONFIG_ENV_SCSI_PART_USE_HW) + if (blk_get_device_part_str("scsi", CONFIG_ENV_SCSI_HW_PARTITION, + &ep->blk, &ep->part, true)) + return NULL; +#endif ep->count = CONFIG_ENV_SIZE / ep->part.blksz; @@ -95,20 +98,24 @@ static int env_scsi_load(void) int ret; if (!ep) { - if (CONFIG_ENV_SCSI_PART_UUID[0] == '\0') - env_set_default("SCSI partition " CONFIG_ENV_SCSI_HW_PARTITION " not found", 0); - else - env_set_default(CONFIG_ENV_SCSI_PART_UUID " partition not found", 0); - +#if defined(CONFIG_ENV_SCSI_PART_USE_TYPE_GUID) + env_set_default("partition type " CONFIG_ENV_SCSI_PART_TYPE_GUID " not found", 0); +#elif defined(CONFIG_ENV_SCSI_PART_USE_UUID) + env_set_default(CONFIG_ENV_SCSI_PART_UUID " partition not found", 0); +#elif defined(CONFIG_ENV_SCSI_PART_USE_HW) + env_set_default("SCSI partition " CONFIG_ENV_SCSI_HW_PARTITION " not found", 0); +#endif return -ENOENT; } if (blk_dread(ep->blk, ep->part.start, ep->count, &envbuf) != ep->count) { - if (CONFIG_ENV_SCSI_PART_UUID[0] == '\0') - env_set_default("SCSI partition " CONFIG_ENV_SCSI_HW_PARTITION " read failed", 0); - else - env_set_default(CONFIG_ENV_SCSI_PART_UUID " partition read failed", 0); - +#if defined(CONFIG_ENV_SCSI_PART_USE_TYPE_GUID) + env_set_default("partition type " CONFIG_ENV_SCSI_PART_TYPE_GUID " read failed", 0); +#elif defined(CONFIG_ENV_SCSI_PART_USE_UUID) + env_set_default(CONFIG_ENV_SCSI_PART_UUID " partition read failed", 0); +#elif defined(CONFIG_ENV_SCSI_PART_USE_HW) + env_set_default("SCSI partition " CONFIG_ENV_SCSI_HW_PARTITION " read failed", 0); +#endif return -EIO; } From ec514a4fc9ca35a725e88b464a3ffe694b46f3cf Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 8 Jan 2026 10:47:20 +0530 Subject: [PATCH 32/35] configs: Enable partition type GUID for QCS9100/QCM6490/QCS615 boards Enable CONFIG_PARTITION_TYPE_GUID and configure SCSI environment partition type GUID for QCS9100, QCM6490 and QCS615 configurations. This allows these platforms to locate the environment partition using GPT type GUID instead of UUID. Signed-off-by: Balaji Selvanathan --- configs/qcm6490_defconfig | 5 +++++ configs/qcom_lemans_defconfig | 4 +++- configs/qcom_qcs615_defconfig | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/configs/qcm6490_defconfig b/configs/qcm6490_defconfig index b088367f86c0..c06b0f9548d7 100644 --- a/configs/qcm6490_defconfig +++ b/configs/qcm6490_defconfig @@ -16,3 +16,8 @@ CONFIG_DEFAULT_DEVICE_TREE="qcom/qcs6490-rb3gen2" CONFIG_FASTBOOT_BUF_ADDR=0xd8800000 CONFIG_PHY_QCOM_QMP_COMBO=y + +CONFIG_ENV_IS_IN_SCSI=y +CONFIG_ENV_SCSI_PART_USE_TYPE_GUID=y +# SCSI partition type GUID for logfs partition +CONFIG_ENV_SCSI_PART_TYPE_GUID="bc0330eb-3410-4951-a617-03898dbe3372" diff --git a/configs/qcom_lemans_defconfig b/configs/qcom_lemans_defconfig index ad40d5206c04..c88e1c9242a3 100644 --- a/configs/qcom_lemans_defconfig +++ b/configs/qcom_lemans_defconfig @@ -11,6 +11,8 @@ CONFIG_REMAKE_ELF=y CONFIG_FASTBOOT_BUF_ADDR=0xdb300000 CONFIG_DEFAULT_DEVICE_TREE="qcom/lemans-evk" CONFIG_ENV_IS_IN_SCSI=y -CONFIG_ENV_SCSI_PART_UUID="71cb9cd0-acf1-b6cb-ad91-be9572fe11a9" +CONFIG_ENV_SCSI_PART_USE_TYPE_GUID=y +# SCSI partition type GUID for logfs partition +CONFIG_ENV_SCSI_PART_TYPE_GUID="bc0330eb-3410-4951-a617-03898dbe3372" # CONFIG_ENV_IS_DEFAULT is not set # CONFIG_ENV_IS_NOWHERE is not set diff --git a/configs/qcom_qcs615_defconfig b/configs/qcom_qcs615_defconfig index 27666a8129d7..021d9d672846 100644 --- a/configs/qcom_qcs615_defconfig +++ b/configs/qcom_qcs615_defconfig @@ -22,3 +22,8 @@ CONFIG_REMAKE_ELF=y CONFIG_TEXT_BASE=0x9fc00000 CONFIG_FASTBOOT_BUF_ADDR=0xa1600000 + +CONFIG_ENV_IS_IN_SCSI=y +CONFIG_ENV_SCSI_PART_USE_TYPE_GUID=y +# SCSI partition type GUID for logfs partition +CONFIG_ENV_SCSI_PART_TYPE_GUID="bc0330eb-3410-4951-a617-03898dbe3372" From ed513e6e31ba3d486a01bf3479158fdd25fad120 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Sat, 18 Apr 2026 15:47:06 +0530 Subject: [PATCH 33/35] test: dm: Add partition type GUID lookup test Add a unit test for the partition type GUID lookup functionality. The test verifies that partitions can be correctly identified by their type GUID, specifically testing the ChromeOS kernel partition lookup. Signed-off-by: Balaji Selvanathan --- test/dm/part.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/dm/part.c b/test/dm/part.c index caae23bd4aad..7a427d3d41dd 100644 --- a/test/dm/part.c +++ b/test/dm/part.c @@ -8,9 +8,13 @@ #include #include #include +#include +#include #include #include +DECLARE_GLOBAL_DATA_PTR; + static int do_test(struct unit_test_state *uts, int expected, const char *part_str, bool whole) { @@ -195,3 +199,48 @@ static int dm_test_part_get_info_by_type(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_part_get_info_by_type, UTF_SCAN_PDATA | UTF_SCAN_FDT); + +static int dm_test_part_get_info_by_type_guid(struct unit_test_state *uts) +{ + struct udevice *dev, *blk_dev; + struct blk_desc *desc; + struct disk_partition info; + ofnode root, node; + int partnum; + + if (!IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) + return -EAGAIN; + + /* Bind the mmc5 node (ChromeOS image with type GUIDs) */ + root = oftree_root(oftree_default()); + node = ofnode_find_subnode(root, "mmc5"); + ut_assert(ofnode_valid(node)); + ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false)); + + /* Get the MMC device (probes it), then walk MMC -> BLK parent link */ + ut_assertok(uclass_get_device_by_seq(UCLASS_MMC, 5, &dev)); + ut_assertok(blk_get_from_parent(dev, &blk_dev)); + desc = dev_get_uclass_plat(blk_dev); + ut_assert(desc); + + /* + * Test: look up the first ChromeOS kernel partition by type GUID. + * In the ChromeOS image KERN_A is the first partition carrying the + * ChromeOS kernel type GUID (fe3a2a5d-...). This is partition 2. + */ + partnum = part_get_info_by_type_guid(desc, + "FE3A2A5D-4F32-41A7-B725-ACCC3285A309", + &info); + ut_asserteq(2, partnum); + ut_asserteq_str("KERN_A", info.name); + + /* Test: non-existent GUID must return -ENOENT */ + ut_asserteq(-ENOENT, + part_get_info_by_type_guid(desc, + "00000000-0000-0000-0000-000000000000", + &info)); + + return 0; +} + +DM_TEST(dm_test_part_get_info_by_type_guid, UTF_SCAN_PDATA | UTF_SCAN_FDT); From 67bfb7dfd753cf082355f75ef541d355c22b3641 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Wed, 29 Apr 2026 18:55:52 +0530 Subject: [PATCH 34/35] mach-snapdragon: Add memory node fixup handlers Add device tree DDR configuration fixup handler infrastructure to support runtime dts modifications. DDR info fixup (qcom_fixup_ddrinfo.c) - Adds DDR configuration information like DDR size. Signed-off-by: Aswin Murugan --- arch/arm/mach-snapdragon/Makefile | 1 + arch/arm/mach-snapdragon/qcom_fixup_ddrinfo.c | 161 ++++++++++++++++++ .../arm/mach-snapdragon/qcom_fixup_handlers.h | 46 +++++ 3 files changed, 208 insertions(+) create mode 100644 arch/arm/mach-snapdragon/qcom_fixup_ddrinfo.c create mode 100644 arch/arm/mach-snapdragon/qcom_fixup_handlers.h diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-snapdragon/Makefile index 343e825c6fdd..d1ebe75c4f4f 100644 --- a/arch/arm/mach-snapdragon/Makefile +++ b/arch/arm/mach-snapdragon/Makefile @@ -4,4 +4,5 @@ obj-y += board.o obj-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += capsule_update.o +obj-$(CONFIG_OF_LIVE) += qcom_fixup_ddrinfo.o obj-$(CONFIG_OF_LIVE) += of_fixup.o diff --git a/arch/arm/mach-snapdragon/qcom_fixup_ddrinfo.c b/arch/arm/mach-snapdragon/qcom_fixup_ddrinfo.c new file mode 100644 index 000000000000..59a0e3c5d82a --- /dev/null +++ b/arch/arm/mach-snapdragon/qcom_fixup_ddrinfo.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * DDRInfo Fixup + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + */ + +#include +#include +#include +#include +#include "qcom_fixup_handlers.h" +#include "qcom-priv.h" + +/* With the new SMEM architecture, SMEM IDs need to be defined in individual + * driver files + */ +#define SMEM_ID_DDRINFO 0x25B // 603 + +struct ddr_details_entry { + u8 manufacturer_id; + u8 device_type; +}; + +/** + * get_ddr_details() - Retrieves the DDR details entry from the SMEM. + * @ddr_detail: The DDR details entry to retrieve. + * + * This function retrieves the DDR details entry from the SMEM and prints it + * out. + * + * Return: 0 on success, -1 on failure + */ +static int get_ddr_details(struct ddr_details_entry *ddr_detail) +{ + void *ddr_table_ptr; + struct udevice *dev; + size_t size; + + if (uclass_get_device(UCLASS_SMEM, 0, &dev) != 0) + return log_msg_ret("Error: uclass_get_device\n", -1); + ddr_table_ptr = smem_get(dev, 0, SMEM_ID_DDRINFO, &size); + + if (!ddr_table_ptr) + return log_msg_ret("Error: invalid DDR Entry\n", -1); + memcpy((void *)ddr_detail, ddr_table_ptr, sizeof(struct ddr_details_entry)); + + return 0; +} + +/** + * set_mem_reg_node() - Sets the ram partition info to memory node + * @fdt_ptr: Pointer to the device tree + * @path_offset: Offset to the memory node + * @start_addr: Start address of the mem partition + * @mem_size: Size of the mem partition + * + * This function is responsible for setting the reg property of memory node + * in the device tree + * + * Return: 0 on success, -1 on failure + */ +static int set_mem_reg_node(struct fdt_header *fdt_ptr, u32 path_offset, + u64 start_addr, u64 mem_size) +{ + int ret; + + log_debug("Mem info start addr: %llx ,size %llx\n", start_addr, mem_size); + ret = fixup_dt_node(fdt_ptr, path_offset, "reg", + (void *)(&start_addr), APPEND_PROP_U64); + if (ret) + return log_msg_ret("Failed to append start_addr details in Reg prop\n", -1); + + ret = fixup_dt_node(fdt_ptr, path_offset, "reg", + (void *)(&mem_size), APPEND_PROP_U64); + if (ret) + return log_msg_ret("Failed to append mem_size details in Reg prop\n", -1); + + return 0; +} + +/** + * set_ram_part_info() - Gets the ram partition info from smem and sets + * it to memory node + * @fdt_ptr: Pointer to the device tree + * @path_offset: Offset to the memory node + * + * This function is responsible for obtaining ram partition information info + * from smem and sets it to the device tree + * tree. + * + * Return: 0 on success, -1 on failure + */ +static int set_ram_part_info(struct fdt_header *fdt_ptr, u32 path_offset) +{ + int ret = 0, res, part; + struct usable_ram_partition_table *rpt; + struct ram_partition_entry *rpe; + + rpt = qcom_get_ram_partitions(); + if (!rpt) + return -1; + rpe = &rpt->ram_part_entry[0]; + + for (part = 0; part < rpt->num_partitions; part++, rpe++) + if (rpe->partition_category == RAM_PARTITION_SDRAM && + rpe->partition_type == RAM_PARTITION_SYS_MEMORY) { + res = set_mem_reg_node(fdt_ptr, path_offset, + rpe->start_address, + rpe->available_length); + if (res) { + log_err("Failed to set Mem info start addr: %llx ,size %llx\n", + rpe->start_address, rpe->available_length); + ret = -1; + } + } + + return ret; +} + +/** + * ddrinfo_fixup_handler() - DDRInfo Fixup handler function + * @fdt_ptr: Pointer to the device tree + * + * This function is responsible for updating the DDR information in the device + * tree. + */ +void ddrinfo_fixup_handler(struct fdt_header *fdt_ptr) +{ + u32 path_offset, ret; + u64 prop_value; + struct ddr_details_entry ddr_details; + + ret = get_ddr_details(&ddr_details); + if (ret) { + log_err("Error getting DDR details\n"); + return; + } + + path_offset = fdt_path_offset(fdt_ptr, "/memory"); + if (path_offset < 0) { + log_err("Error getting memory offset: %d\n", path_offset); + return; + } + + prop_value = (u64)ddr_details.device_type; + ret = fixup_dt_node(fdt_ptr, path_offset, "ddr_device_type", + (void *)(&prop_value), APPEND_PROP_U64); + if (ret) + log_err("Failed to append DDR device type data : %d\n", ret); + + ret = fdt_delprop(fdt_ptr, path_offset, "reg"); + if (!ret) { + ret = set_ram_part_info(fdt_ptr, path_offset); + if (ret) + log_err("set_ram_part_info failed"); + } +} + +/* End of File */ diff --git a/arch/arm/mach-snapdragon/qcom_fixup_handlers.h b/arch/arm/mach-snapdragon/qcom_fixup_handlers.h new file mode 100644 index 000000000000..60c5cf6f0996 --- /dev/null +++ b/arch/arm/mach-snapdragon/qcom_fixup_handlers.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SUBSET Parts Fixup: A tool for fixing up subset parts in a system + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + */ + +#include + +enum fdt_fixup_type { + APPEND_PROP_U32 = 0, + APPEND_PROP_U64 = 1, + SET_PROP_U32 = 2, + SET_PROP_U64 = 3, + SET_PROP_STRING = 4, + ADD_SUBNODE = 5, +}; + +/** + * ddrinfo_fixup_handler() - DDRInfo Fixup handler function + * @fdt_ptr: Pointer to the device tree + * + * This function is responsible for updating the DDR information in + * the device tree. + * + * Return: None + */ +void ddrinfo_fixup_handler(struct fdt_header *fdt_ptr); + +/** + * fixup_dt_node() - Exports a property to the firmware DT node. + * @fdt_ptr: The firmware DT node to update. + * @node_offset: The offset in the DT node where the property should be set. + * @property_name: The name of the property to set. + * @property_value: The value of the property to set. + * @type: Fixup type + * This function sets a property in the firmware DT node with the given name and + * value. + * + * Return: 0 on success, negative on failure. + */ +int fixup_dt_node(void *fdt_ptr, int node_offset, + const char *property_name, + void *property_value, + enum fdt_fixup_type type); From 8f76c2f09068769f5c1a7b7ed311246ee00b4b41 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Tue, 13 Jan 2026 17:24:04 +0530 Subject: [PATCH 35/35] arm: snapdragon: Integrate OF_LIVE fixup handlers in ft_board_setup() Integrate the device tree fixup handlers into ft_board_setup() to enable runtime device tree modifications for Qualcomm Snapdragon platforms. This patch adds: 1. ft_board_setup() implementation - Calls all registered fixup handlers - Provides central point for device tree modifications - Executes before passing DT to kernel 2. fixup_dt_node() utility function - Generic device tree node manipulation helper - Supports multiple operation types: * APPEND_PROP_U32/U64: Append 32/64-bit properties * SET_PROP_U32/U64/STRING: Set property values * ADD_SUBNODE: Add new device tree nodes - Automatically adjusts FDT size as needed Signed-off-by: Aswin Murugan --- arch/arm/mach-snapdragon/of_fixup.c | 82 +++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/arch/arm/mach-snapdragon/of_fixup.c b/arch/arm/mach-snapdragon/of_fixup.c index 374e6262db43..c1a0a92834b4 100644 --- a/arch/arm/mach-snapdragon/of_fixup.c +++ b/arch/arm/mach-snapdragon/of_fixup.c @@ -30,6 +30,7 @@ #include #include #include +#include "qcom_fixup_handlers.h" /** * find_ssphy_node() - Find the super-speed PHY node referenced by DWC3 @@ -283,5 +284,86 @@ EVENT_SPY_FULL(EVT_OF_LIVE_BUILT, qcom_of_fixup_nodes); int ft_board_setup(void __maybe_unused *blob, struct bd_info __maybe_unused *bd) { + struct fdt_header *fdt = blob; + + ddrinfo_fixup_handler(fdt); + return 0; } + +int fixup_dt_node(void *fdt_ptr, int node_offset, + const char *property_name, + void *property_value, + enum fdt_fixup_type type) +{ + int ret; + + if ((!fdt_ptr || node_offset < 0) || + (!property_value && type != ADD_SUBNODE)) + return -1; + + switch (type) { + case APPEND_PROP_U32: + fdt_set_totalsize(fdt_ptr, + (fdt_totalsize(fdt_ptr) + + sizeof(struct fdt_property) + + strlen(property_name) + 3 + + sizeof(u32))); + ret = fdt_appendprop_u32(fdt_ptr, node_offset, + property_name, + *(u32 *)property_value); + break; + case APPEND_PROP_U64: + fdt_set_totalsize(fdt_ptr, + (fdt_totalsize(fdt_ptr) + + sizeof(struct fdt_property) + + strlen(property_name) + 3 + + sizeof(u64))); + ret = fdt_appendprop_u64(fdt_ptr, node_offset, + property_name, + *(u64 *)property_value); + break; + case SET_PROP_U32: + fdt_set_totalsize(fdt_ptr, + (fdt_totalsize(fdt_ptr) + + sizeof(struct fdt_property) + + strlen(property_name) + 3 + + sizeof(u32))); + ret = fdt_setprop_u32(fdt_ptr, node_offset, + property_name, + *(u32 *)property_value); + break; + case SET_PROP_U64: + fdt_set_totalsize(fdt_ptr, + (fdt_totalsize(fdt_ptr) + + sizeof(struct fdt_property) + + strlen(property_name) + 3 + + sizeof(u64))); + ret = fdt_setprop_u64(fdt_ptr, node_offset, + property_name, + *(u64 *)property_value); + break; + case SET_PROP_STRING: + fdt_set_totalsize(fdt_ptr, + (fdt_totalsize(fdt_ptr) + + sizeof(struct fdt_property) + + strlen(property_name) + 3 + + strlen((char *)property_value))); + ret = fdt_setprop_string(fdt_ptr, node_offset, + property_name, + (char *)property_value); + break; + case ADD_SUBNODE: + fdt_set_totalsize(fdt_ptr, + (fdt_totalsize(fdt_ptr) + + sizeof(struct fdt_property) + + strlen(property_name) + 3)); + ret = fdt_add_subnode(fdt_ptr, node_offset, + property_name); + break; + default: + ret = -1; + } + + return ret; +}