From aa8d643e5f69fedc4d572da9600b188953be31da Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Tue, 24 Mar 2026 11:22:34 +0530 Subject: [PATCH 01/70] 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 9be75848ea55f6a5f69314220c42ea1696bfa810 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Tue, 24 Mar 2026 11:22:35 +0530 Subject: [PATCH 02/70] 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 aaa87923604b..3b36eb897f97 100644 --- a/drivers/mmc/msm_sdhci.c +++ b/drivers/mmc/msm_sdhci.c @@ -188,6 +188,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; @@ -251,6 +252,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 7cc234de14f6d7c457da87d6034f41a356b5dacf Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Tue, 21 Apr 2026 18:16:09 +0530 Subject: [PATCH 03/70] 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 d0d54ec5a70b..9694a3dec217 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 9d3d098ab17119d66447dd7625d179c8e59b4acf Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 13 Feb 2026 16:57:13 +0530 Subject: [PATCH 04/70] 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 14ac7ca5ba221f94ba5665f8319750adf58c4adc Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 13 Feb 2026 16:57:14 +0530 Subject: [PATCH 05/70] 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 2d4183a5b34104c9b33db69163252bc8bffdc923 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Wed, 15 Apr 2026 14:33:09 +0530 Subject: [PATCH 06/70] 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 f499eb457a0f612705d975fa3fc7dfd70bd39b26 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Wed, 15 Apr 2026 14:39:36 +0530 Subject: [PATCH 07/70] 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 e8e46e9b9023c1b092d59948a25b46010d2616d2 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 13 Feb 2026 16:57:17 +0530 Subject: [PATCH 08/70] 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 9694a3dec217..2483aaaedb8f 100644 --- a/configs/qcom_defconfig +++ b/configs/qcom_defconfig @@ -120,6 +120,7 @@ CONFIG_PINCTRL_QCOM_GENERIC=y CONFIG_QCOM_RPMH_POWER_DOMAIN=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_MSM_GENI_SERIAL=y CONFIG_SOC_QCOM=y CONFIG_QCOM_COMMAND_DB=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 d1bda4c2f5a360befc8019c2a76127a5ce72e2e4 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Mon, 30 Mar 2026 17:57:47 +0530 Subject: [PATCH 09/70] 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 0887de4333b0..769f43efe4d5 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -1021,6 +1021,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"; }; @@ -1038,6 +1043,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 d69b0e08d66a..ca2401dabc1e 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 880d0fed3e074b93b7a3d2b2dc0ae0f2c9b2982b Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Mon, 30 Mar 2026 18:09:30 +0530 Subject: [PATCH 10/70] 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 6e4dce01067da870d2885039f48a10f7e8813656 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 13 Feb 2026 16:57:16 +0530 Subject: [PATCH 11/70] 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 d4255698231dc55a70f37d185ebb84d35e4041c1 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 10:25:13 +0530 Subject: [PATCH 12/70] 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 0db23b115e2f25a9f8d3e206fc22c222914da483 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:10:59 +0530 Subject: [PATCH 13/70] 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 603853ef276680f8fb7898ef6162febdd3968f40 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:26:37 +0530 Subject: [PATCH 14/70] 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 d7a36b5cf6bcb2ca677acc59ff82ca7b7af50ba7 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:29:43 +0530 Subject: [PATCH 15/70] 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 1414ad3c82df3f5ba31e01e3fe4994123e9fa176 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:30:36 +0530 Subject: [PATCH 16/70] 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 09777f5a7c068298f400432dad97ea9dbf6f9432 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 19 Mar 2026 12:31:26 +0530 Subject: [PATCH 17/70] 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 69c739d80d8d2373e8e14c57b8e7b813902e9ae1 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 10:02:51 +0530 Subject: [PATCH 18/70] 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 89cc83ced71455aed40b5d094cffd06fe3ba4e56 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 24 Apr 2026 12:00:17 +0530 Subject: [PATCH 19/70] 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 6174fba258ad6ec0350450ec7ec8542986c72d4c Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 24 Apr 2026 12:03:38 +0530 Subject: [PATCH 20/70] 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 2bc685dc8981eedefff53a68abccacff0a678b07 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 24 Apr 2026 12:07:04 +0530 Subject: [PATCH 21/70] 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 523c6393d0d54c81b6f22ed5a401930a1634a8bc Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 10 Apr 2026 10:02:50 +0530 Subject: [PATCH 22/70] 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 ad1a08dad8647fca1b74fcb0e793266ab82ce1c0 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 17 Apr 2026 15:09:24 +0530 Subject: [PATCH 23/70] 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 6209bed3ab765f63b2f9ac9a273a38468b995601 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 5 Feb 2026 14:42:20 +0530 Subject: [PATCH 24/70] 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 2483aaaedb8f..af2b8f8e973e 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 49a82c53dbca24b8ddde2ac13297a0cfe30bf793 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 8 Jan 2026 10:35:30 +0530 Subject: [PATCH 25/70] 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 a15478bbf7a8a83cc6a005c13be8bba622f28381 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 20:57:44 +0530 Subject: [PATCH 26/70] 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 44833831ed2ed630fb6c9bb6a0872be44ac99dc4 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 20:59:29 +0530 Subject: [PATCH 27/70] 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 b561ad937f85d80c825e158d09fdbc8c66b26d44 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 21:30:26 +0530 Subject: [PATCH 28/70] 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 2f376f18d7f6fadc3bbe360136224b6baf125f7b Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 21:40:57 +0530 Subject: [PATCH 29/70] 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 dfadcd76e1f8fe90eb644be5637c0b93f6c3d42a Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Mon, 27 Apr 2026 21:47:10 +0530 Subject: [PATCH 30/70] 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 91103045081e2eb39dde6d47f3e5010a4b9a1f9e Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Thu, 8 Jan 2026 10:47:20 +0530 Subject: [PATCH 31/70] 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 7ccdf30fb559604009ea53eedf6ab0da215f7fd5 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Sat, 18 Apr 2026 15:47:06 +0530 Subject: [PATCH 32/70] 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 b9b6584d15f4e9aa1f3165e8358f184e34329760 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Sun, 8 Mar 2026 23:00:09 +0530 Subject: [PATCH 33/70] spmi: msm: Allow writes to SDAM02 reboot mode peripheral Add an exception to bypass the read-only check for the SDAM02 reboot mode peripheral (slave_id=0, pid=0x71) in the SPMI write function. This peripheral needs to be writable to store reboot reason information, even when the channel is marked as read-only due to ownership by a different execution environment. The change adds: - SDAM02_REBOOT_MODE_PID definition (0x71) - Runtime check in msm_spmi_write() to skip read-only enforcement for this specific peripheral This allows the reboot mode to be written successfully while maintaining read-only protection for all other peripherals. This is a workarround and proper fix needs to be identified & upstreamed Signed-off-by: Aswin Murugan --- drivers/spmi/spmi-msm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/spmi/spmi-msm.c b/drivers/spmi/spmi-msm.c index b89dd0b406ba..da399685f7b2 100644 --- a/drivers/spmi/spmi-msm.c +++ b/drivers/spmi/spmi-msm.c @@ -59,6 +59,8 @@ #define SPMI_MAX_SLAVES 16 #define SPMI_MAX_PERIPH 256 +#define SDAM02_REBOOT_MODE_PID 0x71 + #define SPMI_CHANNEL_READ_ONLY BIT(31) #define SPMI_CHANNEL_VALID BIT(30) #define SPMI_CHANNEL_MASK 0xffff @@ -117,8 +119,11 @@ static int msm_spmi_write(struct udevice *dev, int usid, int pid, int off, return -EIO; if (!(priv->channel_map[usid][pid] & SPMI_CHANNEL_VALID)) return -EINVAL; - if (priv->channel_map[usid][pid] & SPMI_CHANNEL_READ_ONLY) - return -EPERM; + /* Skip readonly check for SDAM02 reboot reason pid */ + if (priv->channel_map[usid][pid] & SPMI_CHANNEL_READ_ONLY) { + if (!(usid == 0 && pid == SDAM02_REBOOT_MODE_PID)) + return -EPERM; + } channel = priv->channel_map[usid][pid] & SPMI_CHANNEL_MASK; From c05bcbdcaec8769dce6c8f94d853b84c2c8b52b1 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Wed, 28 Jan 2026 11:57:19 +0530 Subject: [PATCH 34/70] arm64: dts: qcom: talos: Flatten usb controller nodes Flatten usb controller nodes and update to using latest bindings and flattened driver approach. Reviewed-by: Dmitry Baryshkov Signed-off-by: Krishna Kurapati Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260128062720.437712-2-krishna.kurapati@oss.qualcomm.com Signed-off-by: Bjorn Andersson [ upstream commit: c1e77b13457d20f14c70e623f7c0a6a3cefc1d67 ] (cherry picked from commit 37a426e144bb6120e5d93563b70dda34f4348b2f) --- dts/upstream/src/arm64/qcom/qcs615-ride.dts | 12 +-- dts/upstream/src/arm64/qcom/talos.dtsi | 94 +++++++++------------ 2 files changed, 43 insertions(+), 63 deletions(-) diff --git a/dts/upstream/src/arm64/qcom/qcs615-ride.dts b/dts/upstream/src/arm64/qcom/qcs615-ride.dts index 5a24c19c415e..4f5dca49e15a 100644 --- a/dts/upstream/src/arm64/qcom/qcs615-ride.dts +++ b/dts/upstream/src/arm64/qcom/qcs615-ride.dts @@ -655,11 +655,9 @@ }; &usb_1 { - status = "okay"; -}; - -&usb_1_dwc3 { dr_mode = "peripheral"; + + status = "okay"; }; &usb_hsphy_2 { @@ -671,11 +669,9 @@ }; &usb_2 { - status = "okay"; -}; - -&usb_2_dwc3 { dr_mode = "host"; + + status = "okay"; }; &ufs_mem_hc { diff --git a/dts/upstream/src/arm64/qcom/talos.dtsi b/dts/upstream/src/arm64/qcom/talos.dtsi index 75716b4a58d6..8b2e8d335d80 100644 --- a/dts/upstream/src/arm64/qcom/talos.dtsi +++ b/dts/upstream/src/arm64/qcom/talos.dtsi @@ -4486,9 +4486,9 @@ status = "disabled"; }; - usb_1: usb@a6f8800 { - compatible = "qcom,qcs615-dwc3", "qcom,dwc3"; - reg = <0x0 0x0a6f8800 0x0 0x400>; + usb_1: usb@a600000 { + compatible = "qcom,qcs615-dwc3", "qcom,snps-dwc3"; + reg = <0x0 0x0a600000 0x0 0xfc100>; clocks = <&gcc GCC_CFG_NOC_USB3_PRIM_AXI_CLK>, <&gcc GCC_USB30_PRIM_MASTER_CLK>, @@ -4507,52 +4507,44 @@ <&gcc GCC_USB30_PRIM_MASTER_CLK>; assigned-clock-rates = <19200000>, <200000000>; - interrupts-extended = <&intc GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH 0>, + interrupts-extended = <&intc GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH 0>, + <&intc GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH 0>, <&intc GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH 0>, <&pdc 9 IRQ_TYPE_EDGE_BOTH>, <&pdc 8 IRQ_TYPE_EDGE_BOTH>, <&pdc 6 IRQ_TYPE_LEVEL_HIGH>; - interrupt-names = "pwr_event", + interrupt-names = "dwc_usb3", + "pwr_event", "hs_phy_irq", "dp_hs_phy_irq", "dm_hs_phy_irq", "ss_phy_irq"; + iommus = <&apps_smmu 0x140 0x0>; + + phys = <&usb_1_hsphy>, <&usb_qmpphy>; + phy-names = "usb2-phy", "usb3-phy"; + power-domains = <&gcc USB30_PRIM_GDSC>; required-opps = <&rpmhpd_opp_nom>; resets = <&gcc GCC_USB30_PRIM_BCR>; - #address-cells = <2>; - #size-cells = <2>; - ranges; + snps,dis-u1-entry-quirk; + snps,dis-u2-entry-quirk; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis_enblslpm_quirk; + snps,has-lpm-erratum; + snps,hird-threshold = /bits/ 8 <0x10>; + snps,usb3_lpm_capable; status = "disabled"; - - usb_1_dwc3: usb@a600000 { - compatible = "snps,dwc3"; - reg = <0x0 0x0a600000 0x0 0xcd00>; - - iommus = <&apps_smmu 0x140 0x0>; - interrupts = ; - - phys = <&usb_1_hsphy>, <&usb_qmpphy>; - phy-names = "usb2-phy", "usb3-phy"; - - snps,dis-u1-entry-quirk; - snps,dis-u2-entry-quirk; - snps,dis_u2_susphy_quirk; - snps,dis_u3_susphy_quirk; - snps,dis_enblslpm_quirk; - snps,has-lpm-erratum; - snps,hird-threshold = /bits/ 8 <0x10>; - snps,usb3_lpm_capable; - }; }; - usb_2: usb@a8f8800 { - compatible = "qcom,qcs615-dwc3", "qcom,dwc3"; - reg = <0x0 0x0a8f8800 0x0 0x400>; + usb_2: usb@a800000 { + compatible = "qcom,qcs615-dwc3", "qcom,snps-dwc3"; + reg = <0x0 0x0a800000 0x0 0xfc100>; clocks = <&gcc GCC_CFG_NOC_USB2_SEC_AXI_CLK>, <&gcc GCC_USB20_SEC_MASTER_CLK>, @@ -4571,15 +4563,22 @@ <&gcc GCC_USB20_SEC_MASTER_CLK>; assigned-clock-rates = <19200000>, <200000000>; - interrupts-extended = <&intc GIC_SPI 663 IRQ_TYPE_LEVEL_HIGH 0>, + interrupts-extended = <&intc GIC_SPI 664 IRQ_TYPE_LEVEL_HIGH 0>, + <&intc GIC_SPI 663 IRQ_TYPE_LEVEL_HIGH 0>, <&intc GIC_SPI 662 IRQ_TYPE_LEVEL_HIGH 0>, <&pdc 11 IRQ_TYPE_EDGE_BOTH>, <&pdc 10 IRQ_TYPE_EDGE_BOTH>; - interrupt-names = "pwr_event", + interrupt-names = "dwc_usb3", + "pwr_event", "hs_phy_irq", "dp_hs_phy_irq", "dm_hs_phy_irq"; + iommus = <&apps_smmu 0xe0 0x0>; + + phys = <&usb_hsphy_2>; + phy-names = "usb2-phy"; + power-domains = <&gcc USB20_SEC_GDSC>; required-opps = <&rpmhpd_opp_nom>; @@ -4587,30 +4586,15 @@ qcom,select-utmi-as-pipe-clk; - #address-cells = <2>; - #size-cells = <2>; - ranges; - - status = "disabled"; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis_enblslpm_quirk; + snps,has-lpm-erratum; + snps,hird-threshold = /bits/ 8 <0x10>; - usb_2_dwc3: usb@a800000 { - compatible = "snps,dwc3"; - reg = <0x0 0x0a800000 0x0 0xcd00>; + maximum-speed = "high-speed"; - iommus = <&apps_smmu 0xe0 0x0>; - interrupts = ; - - phys = <&usb_hsphy_2>; - phy-names = "usb2-phy"; - - snps,dis_u2_susphy_quirk; - snps,dis_u3_susphy_quirk; - snps,dis_enblslpm_quirk; - snps,has-lpm-erratum; - snps,hird-threshold = /bits/ 8 <0x10>; - - maximum-speed = "high-speed"; - }; + status = "disabled"; }; tsens0: thermal-sensor@c263000 { From c5a7bafc8bba29f9ee45ff6fc0519c3da4a253c3 Mon Sep 17 00:00:00 2001 From: Sudarshan Shetty Date: Tue, 31 Mar 2026 11:31:06 +0530 Subject: [PATCH 35/70] arm64: dts: qcom: talos/qcs615-ride: Fix inconsistent USB PHY node naming The USB PHY nodes has inconsistent labels as 'usb_1_hsphy' and 'usb_hsphy_2' across talos.dtsi and qcs615-ride.dts. This patch renames them to follow a consistent naming scheme. No functional changes, only label renaming. Reviewed-by: Dmitry Baryshkov Signed-off-by: Sudarshan Shetty Reviewed-by: Konrad Dybcio Link: https://lore.kernel.org/r/20260331060107.501561-3-tessolveupstream@gmail.com Signed-off-by: Bjorn Andersson [ upstream commit: 83a679d61e8d18787ddc6c2f406546bb80dd2a8d ] (cherry picked from commit 06026c986a14e0b01757e78078e1c15b44840e5b) --- dts/upstream/src/arm64/qcom/qcs615-ride.dts | 2 +- dts/upstream/src/arm64/qcom/talos.dtsi | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dts/upstream/src/arm64/qcom/qcs615-ride.dts b/dts/upstream/src/arm64/qcom/qcs615-ride.dts index 4f5dca49e15a..c2c4db527f04 100644 --- a/dts/upstream/src/arm64/qcom/qcs615-ride.dts +++ b/dts/upstream/src/arm64/qcom/qcs615-ride.dts @@ -660,7 +660,7 @@ status = "okay"; }; -&usb_hsphy_2 { +&usb_2_hsphy { vdd-supply = <&vreg_l5a>; vdda-pll-supply = <&vreg_l12a>; vdda-phy-dpdm-supply = <&vreg_l13a>; diff --git a/dts/upstream/src/arm64/qcom/talos.dtsi b/dts/upstream/src/arm64/qcom/talos.dtsi index 8b2e8d335d80..79265436c718 100644 --- a/dts/upstream/src/arm64/qcom/talos.dtsi +++ b/dts/upstream/src/arm64/qcom/talos.dtsi @@ -4417,7 +4417,7 @@ status = "disabled"; }; - usb_hsphy_2: phy@88e3000 { + usb_2_hsphy: phy@88e3000 { compatible = "qcom,qcs615-qusb2-phy"; reg = <0x0 0x088e3000 0x0 0x180>; @@ -4576,7 +4576,7 @@ iommus = <&apps_smmu 0xe0 0x0>; - phys = <&usb_hsphy_2>; + phys = <&usb_2_hsphy>; phy-names = "usb2-phy"; power-domains = <&gcc USB20_SEC_GDSC>; From 423dde7b6822956bcb3dc90db31a6c964ec15a39 Mon Sep 17 00:00:00 2001 From: Sudarshan Shetty Date: Tue, 31 Mar 2026 11:31:07 +0530 Subject: [PATCH 36/70] arm64: dts: qcom: talos-evk: Add support for QCS615 talos evk board Add the device tree for the QCS615-based Talos EVK platform. The platform is composed of a System-on-Module following the SMARC standard, and a Carrier Board. The Carrier Board supports several display configurations, HDMI and LVDS. Both configurations use the same base hardware, with the display selection controlled by a DIP switch. Use a DTBO file, talos-evk-lvds-auo,g133han01.dtso, which defines an overlay that disables HDMI and adds LVDS. The DTs file talos-evk can describe the HDMI display configurations. According to the hardware design and vendor guidance, the WiFi PA supplies VDD_PA_A and VDD_PA_B only need to be enabled at the same time as asserting WLAN_EN. On this platform, WiFi enablement is controlled via the WLAN_EN GPIO (GPIO84), which also drives the VDD_PA_A and VDD_PA_B power enables. Remove the VDD_PA_A and VDD_PA_B regulator nodes from the device tree and rely on WLAN_EN to enable WiFi functionality. Add talos-evk-usb1-peripheral.dtso overlay to enable USB0 peripheral (EDL) mode. The base DTS will keep USB0 host-only due to hardware routing through the EDL DIP switch, and the overlay switches the configuration for device-mode operation. The LVDS backlight hardware has been updated to use a simplified design. The backlight enable signal is now permanently pulled up to 3.3V and is no longer controlled via GPIO59. Remove the GPIO59 based backlight configuration from the device tree, as it is no longer routed to the LVDS interface. The initial device tree includes support for: - CPU and memory - UART - GPIOs - Regulators - PMIC - Early console - AT24MAC602 EEPROM - MCP2515 SPI to CAN - ADV7535 DSI-to-HDMI bridge - DisplayPort interface - SN65DSI84ZXHR DSI-to-LVDS bridge - Wi-Fi/BT Reviewed-by: Dmitry Baryshkov Reviewed-by: Konrad Dybcio Signed-off-by: Sudarshan Shetty Link: https://lore.kernel.org/r/20260331060107.501561-4-tessolveupstream@gmail.com Signed-off-by: Bjorn Andersson [ upstream commit: 0f9e6db8a2237e41209322017e2e9c45729d6d45 ] (cherry picked from commit fa4bb8693709d0ceb81def99a3a8882453369c35) --- .../qcom/talos-evk-lvds-auo,g133han01.dtso | 127 ++++ .../src/arm64/qcom/talos-evk-som.dtsi | 610 ++++++++++++++++++ .../arm64/qcom/talos-evk-usb1-peripheral.dtso | 27 + dts/upstream/src/arm64/qcom/talos-evk.dts | 139 ++++ 4 files changed, 903 insertions(+) create mode 100644 dts/upstream/src/arm64/qcom/talos-evk-lvds-auo,g133han01.dtso create mode 100644 dts/upstream/src/arm64/qcom/talos-evk-som.dtsi create mode 100644 dts/upstream/src/arm64/qcom/talos-evk-usb1-peripheral.dtso create mode 100644 dts/upstream/src/arm64/qcom/talos-evk.dts diff --git a/dts/upstream/src/arm64/qcom/talos-evk-lvds-auo,g133han01.dtso b/dts/upstream/src/arm64/qcom/talos-evk-lvds-auo,g133han01.dtso new file mode 100644 index 000000000000..8d16ce4a61d2 --- /dev/null +++ b/dts/upstream/src/arm64/qcom/talos-evk-lvds-auo,g133han01.dtso @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +/dts-v1/; +/plugin/; + +#include + +&{/} { + backlight: backlight { + compatible = "gpio-backlight"; + gpios = <&tlmm 115 GPIO_ACTIVE_HIGH>; + default-on; + }; + + panel-lvds { + compatible = "auo,g133han01"; + power-supply = <&vreg_v3p3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* LVDS A (Odd pixels) */ + port@0 { + reg = <0>; + dual-lvds-odd-pixels; + + lvds_panel_out_a: endpoint { + remote-endpoint = <&sn65dsi84_out_a>; + }; + }; + + /* LVDS B (Even pixels) */ + port@1 { + reg = <1>; + dual-lvds-even-pixels; + + lvds_panel_out_b: endpoint { + remote-endpoint = <&sn65dsi84_out_b>; + }; + }; + }; + }; + + vreg_v3p3: regulator-v3p3 { + compatible = "regulator-fixed"; + regulator-name = "vdd-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; +}; + +&hdmi_connector { + status = "disabled"; +}; + +&i2c1 { + clock-frequency = <400000>; + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + + hdmi_bridge: bridge@3d { + reg = <0x3d>; + status = "disabled"; + }; + + lvds_bridge: bridge@2c { + compatible = "ti,sn65dsi84"; + reg = <0x2c>; + enable-gpios = <&tlmm 42 GPIO_ACTIVE_HIGH>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + sn65dsi84_in: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&mdss_dsi0_out>; + }; + }; + + port@2 { + reg = <2>; + + sn65dsi84_out_a: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&lvds_panel_out_a>; + }; + }; + + port@3 { + reg = <3>; + + sn65dsi84_out_b: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&lvds_panel_out_b>; + }; + }; + }; + }; +}; + +&mdss_dsi0 { + vdda-supply = <&vreg_l11a>; + + status = "okay"; +}; + +&mdss_dsi0_out { + remote-endpoint = <&sn65dsi84_in>; + data-lanes = <0 1 2 3>; +}; + +&tlmm { + lcd_bklt_en: lcd-bklt-en-state { + pins = "gpio115"; + function = "gpio"; + bias-disable; + }; +}; diff --git a/dts/upstream/src/arm64/qcom/talos-evk-som.dtsi b/dts/upstream/src/arm64/qcom/talos-evk-som.dtsi new file mode 100644 index 000000000000..1720f175719a --- /dev/null +++ b/dts/upstream/src/arm64/qcom/talos-evk-som.dtsi @@ -0,0 +1,610 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +/dts-v1/; + +#include +#include +#include "talos.dtsi" +#include "pm8150.dtsi" +/ { + aliases { + i2c1 = &i2c1; + i2c5 = &i2c5; + mmc0 = &sdhc_1; + serial0 = &uart0; + serial1 = &uart7; + spi6 = &spi6; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + clocks { + can_osc: can-oscillator { + compatible = "fixed-clock"; + clock-frequency = <20000000>; + #clock-cells = <0>; + }; + + sleep_clk: sleep-clk { + compatible = "fixed-clock"; + clock-frequency = <32764>; + #clock-cells = <0>; + }; + + xo_board_clk: xo-board-clk { + compatible = "fixed-clock"; + clock-frequency = <38400000>; + #clock-cells = <0>; + }; + }; + + vreg_conn_1p8: regulator-conn-1p8 { + compatible = "regulator-fixed"; + regulator-name = "vreg_conn_1p8"; + startup-delay-us = <4000>; + enable-active-high; + gpio = <&pm8150_gpios 1 GPIO_ACTIVE_HIGH>; + }; + + vreg_conn_pa: regulator-conn-pa { + compatible = "regulator-fixed"; + regulator-name = "vreg_conn_pa"; + startup-delay-us = <4000>; + enable-active-high; + gpio = <&pm8150_gpios 6 GPIO_ACTIVE_HIGH>; + }; + + regulator-usb2-vbus { + compatible = "regulator-fixed"; + regulator-name = "USB2_VBUS"; + gpio = <&pm8150_gpios 10 GPIO_ACTIVE_HIGH>; + pinctrl-0 = <&usb2_en>; + pinctrl-names = "default"; + enable-active-high; + regulator-always-on; + }; + + vreg_v3p3_can: regulator-v3p3-can { + compatible = "regulator-fixed"; + regulator-name = "vreg-v3p3-can"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + vreg_v5p0_can: regulator-v5p0-can { + compatible = "regulator-fixed"; + regulator-name = "vreg-v5p0-can"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + regulator-always-on; + }; + + wcn6855-pmu { + compatible = "qcom,wcn6855-pmu"; + + pinctrl-0 = <&bt_en_state>, <&wlan_en_state>; + pinctrl-names = "default"; + + bt-enable-gpios = <&tlmm 85 GPIO_ACTIVE_HIGH>; + wlan-enable-gpios = <&tlmm 84 GPIO_ACTIVE_HIGH>; + + vddio-supply = <&vreg_conn_pa>; + vddaon-supply = <&vreg_s5a>; + vddpmu-supply = <&vreg_conn_1p8>; + vddpmumx-supply = <&vreg_conn_1p8>; + vddpmucx-supply = <&vreg_conn_pa>; + vddrfa0p95-supply = <&vreg_s5a>; + vddrfa1p3-supply = <&vreg_s6a>; + vddrfa1p9-supply = <&vreg_l15a>; + vddpcie1p3-supply = <&vreg_s6a>; + vddpcie1p9-supply = <&vreg_l15a>; + + regulators { + vreg_pmu_rfa_cmn: ldo0 { + regulator-name = "vreg_pmu_rfa_cmn"; + }; + + vreg_pmu_aon_0p59: ldo1 { + regulator-name = "vreg_pmu_aon_0p59"; + }; + + vreg_pmu_wlcx_0p8: ldo2 { + regulator-name = "vreg_pmu_wlcx_0p8"; + }; + + vreg_pmu_wlmx_0p85: ldo3 { + regulator-name = "vreg_pmu_wlmx_0p85"; + }; + + vreg_pmu_btcmx_0p85: ldo4 { + regulator-name = "vreg_pmu_btcmx_0p85"; + }; + + vreg_pmu_rfa_0p8: ldo5 { + regulator-name = "vreg_pmu_rfa_0p8"; + }; + + vreg_pmu_rfa_1p2: ldo6 { + regulator-name = "vreg_pmu_rfa_1p2"; + }; + + vreg_pmu_rfa_1p7: ldo7 { + regulator-name = "vreg_pmu_rfa_1p7"; + }; + + vreg_pmu_pcie_0p9: ldo8 { + regulator-name = "vreg_pmu_pcie_0p9"; + }; + + vreg_pmu_pcie_1p8: ldo9 { + regulator-name = "vreg_pmu_pcie_1p8"; + }; + }; + }; +}; + +&apps_rsc { + regulators-0 { + compatible = "qcom,pm8150-rpmh-regulators"; + qcom,pmic-id = "a"; + + vreg_s3a: smps3 { + regulator-name = "vreg_s3a"; + regulator-min-microvolt = <600000>; + regulator-max-microvolt = <650000>; + regulator-initial-mode = ; + }; + + vreg_s4a: smps4 { + regulator-name = "vreg_s4a"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1829000>; + regulator-initial-mode = ; + }; + + vreg_s5a: smps5 { + regulator-name = "vreg_s5a"; + regulator-min-microvolt = <1896000>; + regulator-max-microvolt = <2040000>; + regulator-initial-mode = ; + }; + + vreg_s6a: smps6 { + regulator-name = "vreg_s6a"; + regulator-min-microvolt = <1304000>; + regulator-max-microvolt = <1404000>; + regulator-initial-mode = ; + }; + + vreg_l1a: ldo1 { + regulator-name = "vreg_l1a"; + regulator-min-microvolt = <488000>; + regulator-max-microvolt = <852000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l2a: ldo2 { + regulator-name = "vreg_l2a"; + regulator-min-microvolt = <1650000>; + regulator-max-microvolt = <3100000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l3a: ldo3 { + regulator-name = "vreg_l3a"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1248000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l5a: ldo5 { + regulator-name = "vreg_l5a"; + regulator-min-microvolt = <875000>; + regulator-max-microvolt = <975000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l7a: ldo7 { + regulator-name = "vreg_l7a"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1900000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l8a: ldo8 { + regulator-name = "vreg_l8a"; + regulator-min-microvolt = <1150000>; + regulator-max-microvolt = <1350000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l10a: ldo10 { + regulator-name = "vreg_l10a"; + regulator-min-microvolt = <2950000>; + regulator-max-microvolt = <3312000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l11a: ldo11 { + regulator-name = "vreg_l11a"; + regulator-min-microvolt = <1232000>; + regulator-max-microvolt = <1260000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l12a: ldo12 { + regulator-name = "vreg_l12a"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1890000>; + regulator-initial-mode = ; + }; + + vreg_l13a: ldo13 { + regulator-name = "vreg_l13a"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3230000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l15a: ldo15 { + regulator-name = "vreg_l15a"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1904000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l16a: ldo16 { + regulator-name = "vreg_l16a"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3312000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l17a: ldo17 { + regulator-name = "vreg_l17a"; + regulator-min-microvolt = <2950000>; + regulator-max-microvolt = <3312000>; + regulator-initial-mode = ; + }; + }; +}; + +&gpi_dma0 { + status = "okay"; +}; + +&gpi_dma1 { + status = "okay"; +}; + +&i2c5 { + clock-frequency = <400000>; + status = "okay"; + + eeprom@57 { + compatible = "atmel,24c02"; + reg = <0x57>; + pagesize = <16>; + }; + + eeprom@5f { + compatible = "atmel,24mac602"; + reg = <0x5f>; + pagesize = <16>; + }; +}; + +&mdss { + status = "okay"; +}; + +&mdss_dp0 { + status = "okay"; +}; + +&mdss_dp0_out { + link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000>; + remote-endpoint = <&dp0_connector_in>; +}; + +&mdss_dsi0 { + vdda-supply = <&vreg_l11a>; + status = "okay"; +}; + +&mdss_dsi0_phy { + vcca-supply = <&vreg_l5a>; + status = "okay"; +}; + +&pcie { + perst-gpios = <&tlmm 89 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 100 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&pcie_default_state>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie_phy { + vdda-phy-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + + status = "okay"; +}; + +&pcie_port0 { + wifi@0 { + compatible = "pci17cb,1103"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + + qcom,calibration-variant = "QC_QCS615_Ride"; + + vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; + vddaon-supply = <&vreg_pmu_aon_0p59>; + vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; + vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; + vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; + vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; + vddrfa1p8-supply = <&vreg_pmu_rfa_1p7>; + vddpcie0p9-supply = <&vreg_pmu_pcie_0p9>; + vddpcie1p8-supply = <&vreg_pmu_pcie_1p8>; + }; +}; + +&pm8150_gpios { + usb2_en: usb2-en-state { + pins = "gpio10"; + function = "normal"; + output-enable; + power-source = <0>; + }; +}; + +&qupv3_id_0 { + status = "okay"; +}; + +&qupv3_id_1 { + status = "okay"; +}; + +&remoteproc_adsp { + firmware-name = "qcom/qcs615/adsp.mbn"; + + status = "okay"; +}; + +&remoteproc_cdsp { + firmware-name = "qcom/qcs615/cdsp.mbn"; + + status = "okay"; +}; + +&sdhc_1 { + pinctrl-0 = <&sdc1_state_on>; + pinctrl-1 = <&sdc1_state_off>; + pinctrl-names = "default", "sleep"; + + bus-width = <8>; + mmc-ddr-1_8v; + mmc-hs200-1_8v; + mmc-hs400-1_8v; + mmc-hs400-enhanced-strobe; + vmmc-supply = <&vreg_l17a>; + vqmmc-supply = <&vreg_s4a>; + + non-removable; + no-sd; + no-sdio; + + status = "okay"; +}; + +&spi6 { + status = "okay"; + + can@0 { + compatible = "microchip,mcp2515"; + reg = <0>; + clocks = <&can_osc>; + interrupts-extended = <&tlmm 87 IRQ_TYPE_LEVEL_LOW>; + spi-max-frequency = <10000000>; + vdd-supply = <&vreg_v3p3_can>; + xceiver-supply = <&vreg_v5p0_can>; + }; +}; + +&tlmm { + bt_en_state: bt-en-state { + pins = "gpio85"; + function = "gpio"; + bias-pull-down; + }; + + pcie_default_state: pcie-default-state { + clkreq-pins { + pins = "gpio90"; + function = "pcie_clk_req"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-pins { + pins = "gpio89"; + function = "gpio"; + drive-strength = <2>; + bias-pull-down; + }; + + wake-pins { + pins = "gpio100"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + wifi_reg_en_pins_state: wifi-reg-en-pins-state { + pins = "gpio91"; + function = "gpio"; + drive-strength = <8>; + output-high; + bias-pull-up; + }; + + wlan_en_state: wlan-en-state { + pins = "gpio84"; + function = "gpio"; + drive-strength = <16>; + bias-pull-up; + }; +}; + +&uart0 { + status = "okay"; +}; + +&uart7 { + status = "okay"; + + bluetooth { + compatible = "qcom,wcn6855-bt"; + firmware-name = "QCA6698/hpnv21", "QCA6698/hpbtfw21.tlv"; + + vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; + vddaon-supply = <&vreg_pmu_aon_0p59>; + vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; + vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; + vddbtcmx-supply = <&vreg_pmu_btcmx_0p85>; + vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; + vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; + vddrfa1p8-supply = <&vreg_pmu_rfa_1p7>; + }; +}; + +/* + * USB0 routing and EDL mode: + * + * The USB0 controller’s HS differential pair is switched (manually) + * between the Micro-USB port for EDL/ADB and the on-board USB 3.0 hub. + * + * During EDL (Emergency Download) mode, the HS lines are explicitly + * routed to the Micro-USB port to allow the SoC to enter device mode + * for flashing. + * + * After EDL the switch is normally toggled so the HS lines stay + * connected to the hub’s Type-A downstream ports, leaving no electrical + * path to the Micro-USB connector — therefore USB0 runs host-only in + * normal runtime and device mode must not be advertised. + * + * USB0 is configured host-only in the base device tree; a separate + * device-tree overlay enables the Micro-USB peripheral configuration for + * ADB. For ADB to work during normal runtime the DIP switch SW1 must be + * manually toggled to the off position (reconnecting the HS pair to the + * Micro-USB port). + */ + +&usb_1 { + dr_mode = "host"; + + status = "okay"; +}; + +&usb_1_hsphy { + vdd-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + vdda-phy-dpdm-supply = <&vreg_l13a>; + + status = "okay"; +}; + +&usb_2 { + dr_mode = "host"; + + status = "okay"; +}; + +&usb_2_hsphy { + vdd-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + vdda-phy-dpdm-supply = <&vreg_l13a>; + + status = "okay"; +}; + +&usb_qmpphy { + vdda-phy-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + + status = "okay"; +}; + +&usb_qmpphy_2 { + vdda-phy-supply = <&vreg_l11a>; + vdda-pll-supply = <&vreg_l5a>; + + status = "okay"; +}; + +&ufs_mem_hc { + reset-gpios = <&tlmm 123 GPIO_ACTIVE_LOW>; + vcc-supply = <&vreg_l17a>; + vcc-max-microamp = <600000>; + vccq2-supply = <&vreg_s4a>; + vccq2-max-microamp = <600000>; + + status = "okay"; +}; + +&ufs_mem_phy { + vdda-phy-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + + status = "okay"; +}; + +&venus { + status = "okay"; +}; diff --git a/dts/upstream/src/arm64/qcom/talos-evk-usb1-peripheral.dtso b/dts/upstream/src/arm64/qcom/talos-evk-usb1-peripheral.dtso new file mode 100644 index 000000000000..7552ecf9d7ee --- /dev/null +++ b/dts/upstream/src/arm64/qcom/talos-evk-usb1-peripheral.dtso @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +/dts-v1/; +/plugin/; + +/* + * USB0 Peripheral Mode Overlay + * + * This overlay switches USB0 from host mode to peripheral mode + * by configuring the USB controller node. + * + * Hardware requirement: + * The DIP switch SW1 must be toggled to reconnect the USB0 HS + * differential pair to the Micro-USB connector instead of the + * on-board USB 3.0 hub. + * + * Without toggling SW1, there is no electrical path to the + * Micro-USB connector and device mode will not function. + */ + +&usb_1 { + dr_mode = "peripheral"; + + status = "okay"; +}; diff --git a/dts/upstream/src/arm64/qcom/talos-evk.dts b/dts/upstream/src/arm64/qcom/talos-evk.dts new file mode 100644 index 000000000000..af100e22beee --- /dev/null +++ b/dts/upstream/src/arm64/qcom/talos-evk.dts @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +/dts-v1/; + +#include "talos-evk-som.dtsi" + +/ { + model = "Qualcomm QCS615 IQ 615 EVK"; + compatible = "qcom,talos-evk", "qcom,qcs615", "qcom,sm6150"; + chassis-type = "embedded"; + + aliases { + mmc1 = &sdhc_2; + }; + + dp0-connector { + compatible = "dp-connector"; + label = "DP0"; + type = "full-size"; + + hpd-gpios = <&tlmm 104 GPIO_ACTIVE_HIGH>; + + port { + dp0_connector_in: endpoint { + remote-endpoint = <&mdss_dp0_out>; + }; + }; + }; + + hdmi_connector: hdmi-out { + compatible = "hdmi-connector"; + type = "d"; + + port { + hdmi_con_out: endpoint { + remote-endpoint = <&adv7535_out>; + }; + }; + }; + + vreg_v1p8_out: regulator-v1p8-out { + compatible = "regulator-fixed"; + regulator-name = "vreg-v1p8-out"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vreg_v5p0_out>; + regulator-boot-on; + regulator-always-on; + }; + + vreg_v3p3_out: regulator-v3p3-out { + compatible = "regulator-fixed"; + regulator-name = "vreg-v3p3-out"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vreg_v5p0_out>; + regulator-boot-on; + regulator-always-on; + }; + + vreg_v5p0_out: regulator-v5p0-out { + compatible = "regulator-fixed"; + regulator-name = "vreg-v5p0-out"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + regulator-always-on; + /* Powered by system 20V rail (USBC_VBUS_IN) */ + }; +}; + +&i2c1 { + clock-frequency = <400000>; + status = "okay"; + + hdmi_bridge: bridge@3d { + compatible = "adi,adv7535"; + reg = <0x3d>; + avdd-supply = <&vreg_v1p8_out>; + dvdd-supply = <&vreg_v1p8_out>; + pvdd-supply = <&vreg_v1p8_out>; + a2vdd-supply = <&vreg_v1p8_out>; + v3p3-supply = <&vreg_v3p3_out>; + interrupts-extended = <&tlmm 26 IRQ_TYPE_LEVEL_LOW>; + adi,dsi-lanes = <4>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + adv7535_in: endpoint { + remote-endpoint = <&mdss_dsi0_out>; + }; + }; + + port@1 { + reg = <1>; + + adv7535_out: endpoint { + remote-endpoint = <&hdmi_con_out>; + }; + }; + }; + }; +}; + +&mdss_dsi0_out { + remote-endpoint = <&adv7535_in>; + data-lanes = <0 1 2 3>; +}; + +&pon_pwrkey { + status = "okay"; +}; + +&pon_resin { + linux,code = ; + + status = "okay"; +}; + +&sdhc_2 { + pinctrl-0 = <&sdc2_state_on>; + pinctrl-1 = <&sdc2_state_off>; + pinctrl-names = "default", "sleep"; + + bus-width = <4>; + cd-gpios = <&tlmm 99 GPIO_ACTIVE_LOW>; + + vmmc-supply = <&vreg_l10a>; + vqmmc-supply = <&vreg_s4a>; + + status = "okay"; +}; From 26fd1e68aa7bac0fb61f56aab7c22bd20a3ddaf0 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Wed, 29 Apr 2026 13:10:04 +0530 Subject: [PATCH 37/70] dts: talos-evk-u-boot: add override dtsi Add initial support for the talos EVK platform based on qcs615 SoC. Define memory layout statically. Signed-off-by: Aswin Murugan --- arch/arm/dts/talos-evk-u-boot.dtsi | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 arch/arm/dts/talos-evk-u-boot.dtsi diff --git a/arch/arm/dts/talos-evk-u-boot.dtsi b/arch/arm/dts/talos-evk-u-boot.dtsi new file mode 100644 index 000000000000..bdcefc123ec0 --- /dev/null +++ b/arch/arm/dts/talos-evk-u-boot.dtsi @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/ { + memory@80000000 { + device_type = "memory"; + reg = <0x0 0x80000000 0x0 0x7a00000>, + <0x0 0x89600000 0x0 0x30100000>, + <0x0 0xc0000000 0x0 0xc0000000>; + }; +}; From 731f069015a9b3ec654dad3e4738254d94937843 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Tue, 28 Apr 2026 18:05:40 +0530 Subject: [PATCH 38/70] configs: qcom615: Talos evk enablement Update the default device tree selection to use the talos-evk DTB in place of qcs615-ride. This reflects the current target hardware and ensures the correct platform description is used during boot and align with the intended boot configuration. Signed-off-by: Aswin Murugan --- configs/qcom_qcs615_defconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/configs/qcom_qcs615_defconfig b/configs/qcom_qcs615_defconfig index 021d9d672846..96344f484be1 100644 --- a/configs/qcom_qcs615_defconfig +++ b/configs/qcom_qcs615_defconfig @@ -14,7 +14,7 @@ CONFIG_DEBUG_UART_BASE=0x880000 CONFIG_DEBUG_UART_MSM_GENI=y CONFIG_DEBUG_UART_CLOCK=7372800 -CONFIG_DEFAULT_DEVICE_TREE="qcom/qcs615-ride" +CONFIG_DEFAULT_DEVICE_TREE="qcom/talos-evk" CONFIG_REMAKE_ELF=y @@ -27,3 +27,6 @@ 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" + +# CONFIG_SCSI is not set + From decdd6b3e8870c182d4087b7f508f09faee09d87 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Tue, 13 Jan 2026 14:36:21 +0530 Subject: [PATCH 39/70] WORKAROUND: dts: Add reserved memory regions and EFI memory mapping Add reserved-memory nodes to Talos EVK and QCS6490 device trees to define platform-specific memory regions that should not be used by the operating system. Implement efi_add_known_memory() function in board.c code to parse reserved-memory nodes from device tree and register them with the EFI memory map. This ensures proper memory protection during EFI boot and prevents conflicts with firmware-reserved regions. This is a workaround and ways should be explored to get reserved memory regions info from SMEM. SMEM will be populated by corresponding teams (XBL, TZ etc.). Signed-off-by: Balaji Selvanathan --- arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi | 26 +++++++++++ arch/arm/dts/talos-evk-u-boot.dtsi | 56 +++++++++++++++++++++++ arch/arm/mach-snapdragon/board.c | 58 ++++++++++++++++++++++++ 3 files changed, 140 insertions(+) diff --git a/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi b/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi index c3ec4a317f7c..b8deaa38f51f 100644 --- a/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi +++ b/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi @@ -15,6 +15,32 @@ <0 0xC3400000 0 0x3CC00000>, <1 0x00000000 1 0x00000000>; }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + axon_dma_mem: axon-dma@80600000 { + reg = <0x0 0x80600000 0x0 0x100000>; + no-map; + }; + + xbl_dt_mem: xbl-dt@80894000 { + reg = <0x0 0x80894000 0x0 0x40000>; + no-map; + }; + + pil_reserved_mem: pil-reserved@84300000 { + reg = <0x0 0x84300000 0x0 0x16b00000>; + no-map; + }; + + display_mem: display@e1000000 { + reg = <0x0 0xe1000000 0x0 0x2400000>; + no-map; + }; + }; }; // RAM Entry 0 : Base 0x0080000000 Size 0x003A800000 diff --git a/arch/arm/dts/talos-evk-u-boot.dtsi b/arch/arm/dts/talos-evk-u-boot.dtsi index bdcefc123ec0..d1ea27206c45 100644 --- a/arch/arm/dts/talos-evk-u-boot.dtsi +++ b/arch/arm/dts/talos-evk-u-boot.dtsi @@ -10,4 +10,60 @@ <0x0 0x89600000 0x0 0x30100000>, <0x0 0xc0000000 0x0 0xc0000000>; }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + hyp_mem: hyp@80000000 { + reg = <0x0 0x80000000 0x0 0x600000>; + no-map; + }; + + xbl_boot_mem: xbl-boot@85d00000 { + reg = <0x0 0x85d00000 0x0 0x200000>; + no-map; + }; + + aop_mem: aop@85f00000 { + reg = <0x0 0x85f00000 0x0 0x20000>; + no-map; + }; + + xbl_dt_mem: xbl-dt@85f40000 { + reg = <0x0 0x85f40000 0x0 0x30000>; + no-map; + }; + + tz_stat_mem: tz-stat@86200000 { + reg = <0x0 0x86200000 0x0 0x100000>; + no-map; + }; + + tags_mem: tags@86300000 { + reg = <0x0 0x86300000 0x0 0x1200000>; + no-map; + }; + + tz_mem: tz@87500000 { + reg = <0x0 0x87500000 0x0 0x500000>; + no-map; + }; + + tzapps_mem: tzapps@87a00000 { + reg = <0x0 0x87a00000 0x0 0x1c00000>; + no-map; + }; + + pil_mem: pil@8ab00000 { + reg = <0x0 0x8ab00000 0x0 0xcc17000>; + no-map; + }; + + secure_dsp_mem: secure-dsp@a0000000 { + reg = <0x0 0xa0000000 0x0 0x1600000>; + no-map; + }; + }; }; diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-snapdragon/board.c index 829a0109ac78..bbf2c2df9b4f 100644 --- a/arch/arm/mach-snapdragon/board.c +++ b/arch/arm/mach-snapdragon/board.c @@ -19,7 +19,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -714,6 +716,62 @@ static void carve_out_reserved_memory(void) } } +/** + * efi_add_known_memory() - Add platform-specific reserved memory to EFI map + * + * This function is called by the EFI memory initialization code to allow + * platforms to add their reserved memory regions to the EFI memory map. + * For Qualcomm platforms, this parses the reserved-memory nodes from the + * device tree and adds them to the EFI memory map. + */ +void efi_add_known_memory(void) +{ + ofnode parent, node; + fdt_addr_t addr; + fdt_size_t size; + const char *name; + + if (!IS_ENABLED(CONFIG_EFI_LOADER)) + return; + + /* Parse reserved-memory nodes from device tree */ + parent = ofnode_path("/reserved-memory"); + if (!ofnode_valid(parent)) { + log_debug("No reserved-memory node found in device tree\n"); + return; + } + + log_debug("Adding reserved-memory regions to EFI memory map\n"); + + ofnode_for_each_subnode(node, parent) { + if (!ofnode_is_enabled(node)) + continue; + + addr = ofnode_get_addr_size_index(node, 0, &size); + if (addr != FDT_ADDR_T_NONE) { + efi_status_t ret; + + name = ofnode_get_name(node); + ret = efi_add_memory_map(addr, size, + EFI_RESERVED_MEMORY_TYPE); + + if (ret != EFI_SUCCESS) { + log_err("Failed to reserve %s (0x%llx-0x%llx): %lu\n", + name ? name : "unknown", + (unsigned long long)addr, + (unsigned long long)(addr + size), + ret & ~EFI_ERROR_MASK); + } else { + log_debug("Reserved %s: 0x%llx-0x%llx (%llu KB)\n", + name ? name : "unknown", + (unsigned long long)addr, + (unsigned long long)(addr + size), + (unsigned long long)(size / 1024)); + } + } + } +} + /* This function open-codes setup_all_pgtables() so that we can * insert additional mappings *before* turning on the MMU. */ From 3dc6a4e47d16aa75af682b96038b54cd2c5c80a0 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 1 May 2026 16:45:21 +0530 Subject: [PATCH 40/70] qcom: qcs615: use MMC for fastboot QCS615 platforms are equipped with eMMC storage only and do not support UFS or other SCSI-backed devices. Override the inherited qcom_defconfig fastboot settings to use the MMC block interface with device 0. Signed-off-by: Balaji Selvanathan --- configs/qcom_qcs615_defconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/configs/qcom_qcs615_defconfig b/configs/qcom_qcs615_defconfig index 96344f484be1..5bf6dc60acff 100644 --- a/configs/qcom_qcs615_defconfig +++ b/configs/qcom_qcs615_defconfig @@ -23,6 +23,10 @@ CONFIG_TEXT_BASE=0x9fc00000 CONFIG_FASTBOOT_BUF_ADDR=0xa1600000 +# QCS615 has eMMC only, not UFS/SCSI - override qcom_defconfig settings +CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME="mmc" +CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID=0 + CONFIG_ENV_IS_IN_SCSI=y CONFIG_ENV_SCSI_PART_USE_TYPE_GUID=y # SCSI partition type GUID for logfs partition From 001cc7d96de0814f5b66e21202c060e28a7da609 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 1 May 2026 16:45:41 +0530 Subject: [PATCH 41/70] qcom: qcs615: store environment in eMMC logfs partition Configure QCS615 to store the U-Boot environment in eMMC using a software-defined partition. The environment is placed in the logfs partition. Signed-off-by: Balaji Selvanathan --- configs/qcom_qcs615_defconfig | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/configs/qcom_qcs615_defconfig b/configs/qcom_qcs615_defconfig index 5bf6dc60acff..718b4276dfe5 100644 --- a/configs/qcom_qcs615_defconfig +++ b/configs/qcom_qcs615_defconfig @@ -27,10 +27,11 @@ CONFIG_FASTBOOT_BUF_ADDR=0xa1600000 CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME="mmc" CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID=0 -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" - # CONFIG_SCSI is not set +# Environment stored in eMMC logfs partition +CONFIG_ENV_IS_IN_MMC=y +CONFIG_ENV_MMC_USE_SW_PARTITION=y +CONFIG_ENV_MMC_SW_PARTITION="logfs" +CONFIG_ENV_OFFSET=0x0 +CONFIG_ENV_SIZE=0x4000 From d343cf19bbfac6e01ca397aeae54269a4856258c Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 19:53:54 +0200 Subject: [PATCH 42/70] armv8: mmu: support unmapping regions with set_one_region() Currently set_one_region() implicitly assumes that we want to map a region and aggressively splits blocks into tables to do this, but when called with PTE_TYPE_FAULT to unmap a currently mapped region it may try to unnecessarily split blocks which doesn't make sense if the entire block should actually be unmapped. In the end it then has to walk every single page and create a bunch of empty tables. Introduce a check for this kind of behaviour and optimise with a fast path, if we're unmapping a region >= the size of this entry then we can just unmap the entire PTE and whatever it contains. This fixes some bogus empty tables being left behind when carving out reserved memory regions on Qualcomm, and should improve the performance of the break-before-make in mmu_change_region_attr(). Signed-off-by: Casey Connolly --- arch/arm/cpu/armv8/cache_v8.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/arch/arm/cpu/armv8/cache_v8.c b/arch/arm/cpu/armv8/cache_v8.c index 39479df7b21f..b6506e8523be 100644 --- a/arch/arm/cpu/armv8/cache_v8.c +++ b/arch/arm/cpu/armv8/cache_v8.c @@ -163,7 +163,7 @@ u64 get_tcr(u64 *pips, u64 *pva_bits) static int pte_type(u64 *pte) { - return *pte & PTE_TYPE_MASK; + return *pte & PTE_TYPE_VALID ? *pte & PTE_TYPE_MASK : PTE_TYPE_FAULT; } /* Returns the LSB number for a PTE on level */ @@ -940,6 +940,18 @@ static u64 set_one_region(u64 start, u64 size, u64 attrs, bool flag, int level) u64 levelsize = 1ULL << levelshift; u64 *pte = find_pte(start, level); + /* + * If we're trying to unmap a region then check if it's already unmapped or if it's bigger + * then the PTE we're looking at right now, in the first case we can do nothing and in the + * second case we just need to unmap this page/block. + * Otherwise we will needlessly create new tables until we have traversed every single page + * in the region. + */ + if (attrs == PTE_TYPE_FAULT && (pte_type(pte) == PTE_TYPE_FAULT || size >= levelsize)) { + *pte &= ~(PMD_ATTRMASK | PTE_TYPE_MASK); + return levelsize; + } + /* Can we can just modify the current level block PTE? */ if (is_aligned(start, size, levelsize)) { if (flag) { @@ -1082,6 +1094,10 @@ void mmu_change_region_attr(phys_addr_t addr, size_t siz, u64 attrs) gd->arch.tlb_addr + gd->arch.tlb_size); __asm_invalidate_tlb_all(); + /* If we were unmapping a region then we're done! */ + if (attrs == PTE_TYPE_FAULT) + return; + mmu_change_region_attr_nobreak(addr, siz, attrs); } From 5435a91a904c654e838075456592d7a3ad4f77b3 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:29 +0200 Subject: [PATCH 43/70] Revert "dm: SMEM (Shared memory) uclass" SMEM is a highly Qualcomm specific interface, while having a dedicated UCLASS for it offers a nice abstraction, for things like memory layout parsing we need to use it before the driver model is available. Therefore, it doesn't make sense to fit SMEM into the driver model. Instead let's adopt a model closer to Linux, and parse SMEM really early during boot (as soon as we have the FDT). This reverts commit 7b384eccc785b596f68448b155cbda26df57fb23. Signed-off-by: Casey Connolly --- arch/arm/Kconfig | 1 - drivers/Kconfig | 2 - drivers/Makefile | 1 - drivers/smem/Kconfig | 25 ----------- drivers/smem/Makefile | 7 --- drivers/smem/smem-uclass.c | 46 ------------------- include/dm/uclass-id.h | 1 - include/smem.h | 90 -------------------------------------- 8 files changed, 173 deletions(-) delete mode 100644 drivers/smem/Kconfig delete mode 100644 drivers/smem/Makefile delete mode 100644 drivers/smem/smem-uclass.c delete mode 100644 include/smem.h diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index f624675eadf8..dadb18e0b8cd 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1142,7 +1142,6 @@ config ARCH_SNAPDRAGON select GPIO_EXTRA_HEADER select MSM_SMEM select OF_CONTROL - select SMEM select SPMI select BOARD_LATE_INIT select OF_BOARD diff --git a/drivers/Kconfig b/drivers/Kconfig index 47606ddc6c8b..aa293df37c57 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -124,8 +124,6 @@ source "drivers/serial/Kconfig" source "drivers/sm/Kconfig" -source "drivers/smem/Kconfig" - source "drivers/sound/Kconfig" source "drivers/soc/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 43d0ba332818..ca3e87ffb102 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -119,7 +119,6 @@ obj-y += reset/ obj-y += input/ obj-y += iommu/ # SOC specific infrastructure drivers. -obj-y += smem/ obj-y += thermal/ obj-$(CONFIG_TEE) += tee/ obj-$(CONFIG_ARM_FFA_TRANSPORT) += firmware/arm-ffa/ diff --git a/drivers/smem/Kconfig b/drivers/smem/Kconfig deleted file mode 100644 index e5d7dcc81b17..000000000000 --- a/drivers/smem/Kconfig +++ /dev/null @@ -1,25 +0,0 @@ -menuconfig SMEM - bool "SMEM (Shared Memory mamanger) support" - -if SMEM - -config SANDBOX_SMEM - bool "Sandbox Shared Memory Manager (SMEM)" - depends on SANDBOX && DM - help - enable SMEM support for sandbox. This is an emulation of a real SMEM - manager. - The sandbox driver allocates a shared memory from the heap and - initialzies it on start. - -config MSM_SMEM - bool "Qualcomm Shared Memory Manager (SMEM)" - depends on DM - depends on ARCH_SNAPDRAGON || ARCH_IPQ40XX - select DEVRES - help - Enable support for the Qualcomm Shared Memory Manager. - The driver provides an interface to items in a heap shared among all - processors in a Qualcomm platform. - -endif # menu "SMEM Support" diff --git a/drivers/smem/Makefile b/drivers/smem/Makefile deleted file mode 100644 index af3e9b50883c..000000000000 --- a/drivers/smem/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0+ -# -# Makefile for the U-Boot SMEM interface drivers - -obj-$(CONFIG_SANDBOX_SMEM) += sandbox_smem.o -obj-$(CONFIG_SMEM) += smem-uclass.o -obj-$(CONFIG_MSM_SMEM) += msm_smem.o diff --git a/drivers/smem/smem-uclass.c b/drivers/smem/smem-uclass.c deleted file mode 100644 index 4dea5cc4bf1c..000000000000 --- a/drivers/smem/smem-uclass.c +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2018 Ramon Fried - */ - -#define LOG_CATEGORY UCLASS_SMEM - -#include -#include - -int smem_alloc(struct udevice *dev, unsigned int host, - unsigned int item, size_t size) -{ - struct smem_ops *ops = smem_get_ops(dev); - - if (!ops->alloc) - return -ENOSYS; - - return ops->alloc(host, item, size); -} - -void *smem_get(struct udevice *dev, unsigned int host, - unsigned int item, size_t *size) -{ - struct smem_ops *ops = smem_get_ops(dev); - - if (!ops->get) - return NULL; - - return ops->get(host, item, size); -} - -int smem_get_free_space(struct udevice *dev, unsigned int host) -{ - struct smem_ops *ops = smem_get_ops(dev); - - if (!ops->get_free_space) - return -ENOSYS; - - return ops->get_free_space(host); -} - -UCLASS_DRIVER(smem) = { - .id = UCLASS_SMEM, - .name = "smem", -}; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 36b5d87c304f..fe0aae2720ca 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -134,7 +134,6 @@ enum uclass_id { UCLASS_SCSI, /* SCSI device */ UCLASS_SERIAL, /* Serial UART */ UCLASS_SIMPLE_BUS, /* Bus with child devices */ - UCLASS_SMEM, /* Shared memory interface */ UCLASS_SOC, /* SOC Device */ UCLASS_SOUND, /* Playing simple sounds */ UCLASS_SPI, /* SPI bus */ diff --git a/include/smem.h b/include/smem.h deleted file mode 100644 index b19c534ebc43..000000000000 --- a/include/smem.h +++ /dev/null @@ -1,90 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * The shared memory system is an allocate-only heap structure that - * consists of one of more memory areas that can be accessed by the processors - * in the SoC. - * - * Allocation can be done globally for all processors or to an individual processor. - * This is controlled by the @host parameter. - * - * Allocation and management of heap can be implemented in various ways, - * The @item parameter should be used as an index/hash to the memory region. - * - * Copyright (c) 2018 Ramon Fried - */ - -#ifndef _smemh_ -#define _smemh_ - -/* struct smem_ops: Operations for the SMEM uclass */ -struct smem_ops { - /** - * alloc() - allocate space for a smem item - * - * @host: remote processor id, or -1 for all processors. - * @item: smem item handle - * @size: number of bytes to be allocated - * @return 0 if OK, -ve on error - */ - int (*alloc)(unsigned int host, - unsigned int item, size_t size); - - /** - * get() - Resolve ptr of size of a smem item - * - * @host: the remote processor, of -1 for all processors. - * @item: smem item handle - * @size: pointer to be filled out with the size of the item - * @return pointer on success, NULL on error - */ - void *(*get)(unsigned int host, - unsigned int item, size_t *size); - - /** - * get_free_space() - Get free space in smem in bytes - * - * @host: the remote processor identifying a partition, or -1 - * for all processors. - * @return free space, -ve on error - */ - int (*get_free_space)(unsigned int host); -}; - -#define smem_get_ops(dev) ((struct smem_ops *)(dev)->driver->ops) - -/** - * smem_alloc() - allocate space for a smem item - * @host: remote processor id, or -1 - * @item: smem item handle - * @size: number of bytes to be allocated - * Return: 0 if OK, -ve on error - * - * Allocate space for a given smem item of size @size, given that the item is - * not yet allocated. - */ -int smem_alloc(struct udevice *dev, unsigned int host, unsigned int item, size_t size); - -/** - * smem_get() - resolve ptr of size of a smem item - * @host: the remote processor, or -1 for all processors. - * @item: smem item handle - * @size: pointer to be filled out with size of the item - * Return: pointer on success, NULL on error - * - * Looks up smem item and returns pointer to it. Size of smem - * item is returned in @size. - */ -void *smem_get(struct udevice *dev, unsigned int host, unsigned int item, size_t *size); - -/** - * smem_get_free_space() - retrieve amount of free space in a partition - * @host: the remote processor identifying a partition, or -1 - * for all processors. - * Return: size in bytes, -ve on error - * - * To be used by smem clients as a quick way to determine if any new - * allocations has been made. - */ -int smem_get_free_space(struct udevice *dev, unsigned int host); - -#endif /* _smem_h_ */ From b89960b19b2337c9031a04771a052b048a6bae23 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:30 +0200 Subject: [PATCH 44/70] smem: drop drivers/smem Remove the old qcom SMEM driver port and the sandbox stub implementation. Signed-off-by: Casey Connolly --- MAINTAINERS | 1 - arch/arm/Kconfig | 1 - drivers/smem/msm_smem.c | 958 ------------------------------------ drivers/smem/sandbox_smem.c | 44 -- 4 files changed, 1004 deletions(-) delete mode 100644 drivers/smem/msm_smem.c delete mode 100644 drivers/smem/sandbox_smem.c diff --git a/MAINTAINERS b/MAINTAINERS index 0dcc7243124e..4d54a61327c7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -688,7 +688,6 @@ F: drivers/phy/msm8916-usbh-phy.c F: drivers/phy/qcom/ F: drivers/serial/serial_msm.c F: drivers/serial/serial_msm_geni.c -F: drivers/smem/msm_smem.c F: drivers/spmi/spmi-msm.c F: drivers/usb/host/ehci-msm.c N: qcom diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index dadb18e0b8cd..a8fcfe76296c 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1140,7 +1140,6 @@ config ARCH_SNAPDRAGON select DM_RESET select POWER_DOMAIN select GPIO_EXTRA_HEADER - select MSM_SMEM select OF_CONTROL select SPMI select BOARD_LATE_INIT diff --git a/drivers/smem/msm_smem.c b/drivers/smem/msm_smem.c deleted file mode 100644 index 7a50d5a5792c..000000000000 --- a/drivers/smem/msm_smem.c +++ /dev/null @@ -1,958 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2015, Sony Mobile Communications AB. - * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. - * Copyright (c) 2018, Ramon Fried - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * The Qualcomm shared memory system is an allocate-only heap structure that - * consists of one of more memory areas that can be accessed by the processors - * in the SoC. - * - * All systems contains a global heap, accessible by all processors in the SoC, - * with a table of contents data structure (@smem_header) at the beginning of - * the main shared memory block. - * - * The global header contains meta data for allocations as well as a fixed list - * of 512 entries (@smem_global_entry) that can be initialized to reference - * parts of the shared memory space. - * - * - * In addition to this global heap, a set of "private" heaps can be set up at - * boot time with access restrictions so that only certain processor pairs can - * access the data. - * - * These partitions are referenced from an optional partition table - * (@smem_ptable), that is found 4kB from the end of the main smem region. The - * partition table entries (@smem_ptable_entry) lists the involved processors - * (or hosts) and their location in the main shared memory region. - * - * Each partition starts with a header (@smem_partition_header) that identifies - * the partition and holds properties for the two internal memory regions. The - * two regions are cached and non-cached memory respectively. Each region - * contain a link list of allocation headers (@smem_private_entry) followed by - * their data. - * - * Items in the non-cached region are allocated from the start of the partition - * while items in the cached region are allocated from the end. The free area - * is hence the region between the cached and non-cached offsets. The header of - * cached items comes after the data. - * - * Version 12 (SMEM_GLOBAL_PART_VERSION) changes the item alloc/get procedure - * for the global heap. A new global partition is created from the global heap - * region with partition type (SMEM_GLOBAL_HOST) and the max smem item count is - * set by the bootloader. - * - */ - -/* - * The version member of the smem header contains an array of versions for the - * various software components in the SoC. We verify that the boot loader - * version is a valid version as a sanity check. - */ -#define SMEM_MASTER_SBL_VERSION_INDEX 7 -#define SMEM_GLOBAL_HEAP_VERSION 11 -#define SMEM_GLOBAL_PART_VERSION 12 - -/* - * The first 8 items are only to be allocated by the boot loader while - * initializing the heap. - */ -#define SMEM_ITEM_LAST_FIXED 8 - -/* Highest accepted item number, for both global and private heaps */ -#define SMEM_ITEM_COUNT 512 - -/* Processor/host identifier for the application processor */ -#define SMEM_HOST_APPS 0 - -/* Processor/host identifier for the global partition */ -#define SMEM_GLOBAL_HOST 0xfffe - -/* Max number of processors/hosts in a system */ -#define SMEM_HOST_COUNT 25 - -/** - * struct smem_proc_comm - proc_comm communication struct (legacy) - * @command: current command to be executed - * @status: status of the currently requested command - * @params: parameters to the command - */ -struct smem_proc_comm { - __le32 command; - __le32 status; - __le32 params[2]; -}; - -/** - * struct smem_global_entry - entry to reference smem items on the heap - * @allocated: boolean to indicate if this entry is used - * @offset: offset to the allocated space - * @size: size of the allocated space, 8 byte aligned - * @aux_base: base address for the memory region used by this unit, or 0 for - * the default region. bits 0,1 are reserved - */ -struct smem_global_entry { - __le32 allocated; - __le32 offset; - __le32 size; - __le32 aux_base; /* bits 1:0 reserved */ -}; -#define AUX_BASE_MASK 0xfffffffc - -/** - * struct smem_header - header found in beginning of primary smem region - * @proc_comm: proc_comm communication interface (legacy) - * @version: array of versions for the various subsystems - * @initialized: boolean to indicate that smem is initialized - * @free_offset: index of the first unallocated byte in smem - * @available: number of bytes available for allocation - * @reserved: reserved field, must be 0 - * toc: array of references to items - */ -struct smem_header { - struct smem_proc_comm proc_comm[4]; - __le32 version[32]; - __le32 initialized; - __le32 free_offset; - __le32 available; - __le32 reserved; - struct smem_global_entry toc[SMEM_ITEM_COUNT]; -}; - -/** - * struct smem_ptable_entry - one entry in the @smem_ptable list - * @offset: offset, within the main shared memory region, of the partition - * @size: size of the partition - * @flags: flags for the partition (currently unused) - * @host0: first processor/host with access to this partition - * @host1: second processor/host with access to this partition - * @cacheline: alignment for "cached" entries - * @reserved: reserved entries for later use - */ -struct smem_ptable_entry { - __le32 offset; - __le32 size; - __le32 flags; - __le16 host0; - __le16 host1; - __le32 cacheline; - __le32 reserved[7]; -}; - -/** - * struct smem_ptable - partition table for the private partitions - * @magic: magic number, must be SMEM_PTABLE_MAGIC - * @version: version of the partition table - * @num_entries: number of partitions in the table - * @reserved: for now reserved entries - * @entry: list of @smem_ptable_entry for the @num_entries partitions - */ -struct smem_ptable { - u8 magic[4]; - __le32 version; - __le32 num_entries; - __le32 reserved[5]; - struct smem_ptable_entry entry[]; -}; - -static const u8 SMEM_PTABLE_MAGIC[] = { 0x24, 0x54, 0x4f, 0x43 }; /* "$TOC" */ - -/** - * struct smem_partition_header - header of the partitions - * @magic: magic number, must be SMEM_PART_MAGIC - * @host0: first processor/host with access to this partition - * @host1: second processor/host with access to this partition - * @size: size of the partition - * @offset_free_uncached: offset to the first free byte of uncached memory in - * this partition - * @offset_free_cached: offset to the first free byte of cached memory in this - * partition - * @reserved: for now reserved entries - */ -struct smem_partition_header { - u8 magic[4]; - __le16 host0; - __le16 host1; - __le32 size; - __le32 offset_free_uncached; - __le32 offset_free_cached; - __le32 reserved[3]; -}; - -static const u8 SMEM_PART_MAGIC[] = { 0x24, 0x50, 0x52, 0x54 }; - -/** - * struct smem_private_entry - header of each item in the private partition - * @canary: magic number, must be SMEM_PRIVATE_CANARY - * @item: identifying number of the smem item - * @size: size of the data, including padding bytes - * @padding_data: number of bytes of padding of data - * @padding_hdr: number of bytes of padding between the header and the data - * @reserved: for now reserved entry - */ -struct smem_private_entry { - u16 canary; /* bytes are the same so no swapping needed */ - __le16 item; - __le32 size; /* includes padding bytes */ - __le16 padding_data; - __le16 padding_hdr; - __le32 reserved; -}; -#define SMEM_PRIVATE_CANARY 0xa5a5 - -/** - * struct smem_info - smem region info located after the table of contents - * @magic: magic number, must be SMEM_INFO_MAGIC - * @size: size of the smem region - * @base_addr: base address of the smem region - * @reserved: for now reserved entry - * @num_items: highest accepted item number - */ -struct smem_info { - u8 magic[4]; - __le32 size; - __le32 base_addr; - __le32 reserved; - __le16 num_items; -}; - -static const u8 SMEM_INFO_MAGIC[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ - -/** - * struct smem_region - representation of a chunk of memory used for smem - * @aux_base: identifier of aux_mem base - * @virt_base: virtual base address of memory with this aux_mem identifier - * @size: size of the memory region - */ -struct smem_region { - u32 aux_base; - void __iomem *virt_base; - size_t size; -}; - -/** - * struct qcom_smem - device data for the smem device - * @dev: device pointer - * @global_partition: pointer to global partition when in use - * @global_cacheline: cacheline size for global partition - * @partitions: list of pointers to partitions affecting the current - * processor/host - * @cacheline: list of cacheline sizes for each host - * @item_count: max accepted item number - * @num_regions: number of @regions - * @regions: list of the memory regions defining the shared memory - */ -struct qcom_smem { - struct udevice *dev; - - struct smem_partition_header *global_partition; - size_t global_cacheline; - struct smem_partition_header *partitions[SMEM_HOST_COUNT]; - size_t cacheline[SMEM_HOST_COUNT]; - u32 item_count; - - unsigned int num_regions; - struct smem_region regions[0]; -}; - -static struct smem_private_entry * -phdr_to_last_uncached_entry(struct smem_partition_header *phdr) -{ - void *p = phdr; - - return p + le32_to_cpu(phdr->offset_free_uncached); -} - -static void *phdr_to_first_cached_entry(struct smem_partition_header *phdr, - size_t cacheline) -{ - void *p = phdr; - - return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*phdr), cacheline); -} - -static void *phdr_to_last_cached_entry(struct smem_partition_header *phdr) -{ - void *p = phdr; - - return p + le32_to_cpu(phdr->offset_free_cached); -} - -static struct smem_private_entry * -phdr_to_first_uncached_entry(struct smem_partition_header *phdr) -{ - void *p = phdr; - - return p + sizeof(*phdr); -} - -static struct smem_private_entry * -uncached_entry_next(struct smem_private_entry *e) -{ - void *p = e; - - return p + sizeof(*e) + le16_to_cpu(e->padding_hdr) + - le32_to_cpu(e->size); -} - -static struct smem_private_entry * -cached_entry_next(struct smem_private_entry *e, size_t cacheline) -{ - void *p = e; - - return p - le32_to_cpu(e->size) - ALIGN(sizeof(*e), cacheline); -} - -static void *uncached_entry_to_item(struct smem_private_entry *e) -{ - void *p = e; - - return p + sizeof(*e) + le16_to_cpu(e->padding_hdr); -} - -static void *cached_entry_to_item(struct smem_private_entry *e) -{ - void *p = e; - - return p - le32_to_cpu(e->size); -} - -/* Pointer to the one and only smem handle */ -static struct qcom_smem *__smem; - -static int qcom_smem_alloc_private(struct qcom_smem *smem, - struct smem_partition_header *phdr, - unsigned int item, - size_t size) -{ - struct smem_private_entry *hdr, *end; - size_t alloc_size; - void *cached; - - hdr = phdr_to_first_uncached_entry(phdr); - end = phdr_to_last_uncached_entry(phdr); - cached = phdr_to_last_cached_entry(phdr); - - while (hdr < end) { - if (hdr->canary != SMEM_PRIVATE_CANARY) { - dev_err(smem->dev, - "Found invalid canary in hosts %d:%d partition\n", - phdr->host0, phdr->host1); - return -EINVAL; - } - - if (le16_to_cpu(hdr->item) == item) - return -EEXIST; - - hdr = uncached_entry_next(hdr); - } - - /* Check that we don't grow into the cached region */ - alloc_size = sizeof(*hdr) + ALIGN(size, 8); - if ((void *)hdr + alloc_size >= cached) { - dev_err(smem->dev, "Out of memory\n"); - return -ENOSPC; - } - - hdr->canary = SMEM_PRIVATE_CANARY; - hdr->item = cpu_to_le16(item); - hdr->size = cpu_to_le32(ALIGN(size, 8)); - hdr->padding_data = cpu_to_le16(le32_to_cpu(hdr->size) - size); - hdr->padding_hdr = 0; - - /* - * Ensure the header is written before we advance the free offset, so - * that remote processors that does not take the remote spinlock still - * gets a consistent view of the linked list. - */ - dmb(); - le32_add_cpu(&phdr->offset_free_uncached, alloc_size); - - return 0; -} - -static int qcom_smem_alloc_global(struct qcom_smem *smem, - unsigned int item, - size_t size) -{ - struct smem_global_entry *entry; - struct smem_header *header; - - header = smem->regions[0].virt_base; - entry = &header->toc[item]; - if (entry->allocated) - return -EEXIST; - - size = ALIGN(size, 8); - if (WARN_ON(size > le32_to_cpu(header->available))) - return -ENOMEM; - - entry->offset = header->free_offset; - entry->size = cpu_to_le32(size); - - /* - * Ensure the header is consistent before we mark the item allocated, - * so that remote processors will get a consistent view of the item - * even though they do not take the spinlock on read. - */ - dmb(); - entry->allocated = cpu_to_le32(1); - - le32_add_cpu(&header->free_offset, size); - le32_add_cpu(&header->available, -size); - - return 0; -} - -/** - * qcom_smem_alloc() - allocate space for a smem item - * @host: remote processor id, or -1 - * @item: smem item handle - * @size: number of bytes to be allocated - * - * Allocate space for a given smem item of size @size, given that the item is - * not yet allocated. - */ -static int qcom_smem_alloc(unsigned int host, unsigned int item, size_t size) -{ - struct smem_partition_header *phdr; - int ret; - - if (!__smem) - return -ENOMEM; - - if (item < SMEM_ITEM_LAST_FIXED) { - dev_err(__smem->dev, - "Rejecting allocation of static entry %d\n", item); - return -EINVAL; - } - - if (WARN_ON(item >= __smem->item_count)) - return -EINVAL; - - if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { - phdr = __smem->partitions[host]; - ret = qcom_smem_alloc_private(__smem, phdr, item, size); - } else if (__smem->global_partition) { - phdr = __smem->global_partition; - ret = qcom_smem_alloc_private(__smem, phdr, item, size); - } else { - ret = qcom_smem_alloc_global(__smem, item, size); - } - - return ret; -} - -static void *qcom_smem_get_global(struct qcom_smem *smem, - unsigned int item, - size_t *size) -{ - struct smem_header *header; - struct smem_region *area; - struct smem_global_entry *entry; - u32 aux_base; - unsigned int i; - - header = smem->regions[0].virt_base; - entry = &header->toc[item]; - if (!entry->allocated) - return ERR_PTR(-ENXIO); - - aux_base = le32_to_cpu(entry->aux_base) & AUX_BASE_MASK; - - for (i = 0; i < smem->num_regions; i++) { - area = &smem->regions[i]; - - if (area->aux_base == aux_base || !aux_base) { - if (size != NULL) - *size = le32_to_cpu(entry->size); - return area->virt_base + le32_to_cpu(entry->offset); - } - } - - return ERR_PTR(-ENOENT); -} - -static void *qcom_smem_get_private(struct qcom_smem *smem, - struct smem_partition_header *phdr, - size_t cacheline, - unsigned int item, - size_t *size) -{ - struct smem_private_entry *e, *end; - - e = phdr_to_first_uncached_entry(phdr); - end = phdr_to_last_uncached_entry(phdr); - - while (e < end) { - if (e->canary != SMEM_PRIVATE_CANARY) - goto invalid_canary; - - if (le16_to_cpu(e->item) == item) { - if (size != NULL) - *size = le32_to_cpu(e->size) - - le16_to_cpu(e->padding_data); - - return uncached_entry_to_item(e); - } - - e = uncached_entry_next(e); - } - - /* Item was not found in the uncached list, search the cached list */ - - e = phdr_to_first_cached_entry(phdr, cacheline); - end = phdr_to_last_cached_entry(phdr); - - while (e > end) { - if (e->canary != SMEM_PRIVATE_CANARY) - goto invalid_canary; - - if (le16_to_cpu(e->item) == item) { - if (size != NULL) - *size = le32_to_cpu(e->size) - - le16_to_cpu(e->padding_data); - - return cached_entry_to_item(e); - } - - e = cached_entry_next(e, cacheline); - } - - return ERR_PTR(-ENOENT); - -invalid_canary: - dev_err(smem->dev, "Found invalid canary in hosts %d:%d partition\n", - phdr->host0, phdr->host1); - - return ERR_PTR(-EINVAL); -} - -/** - * qcom_smem_get() - resolve ptr of size of a smem item - * @host: the remote processor, or -1 - * @item: smem item handle - * @size: pointer to be filled out with size of the item - * - * Looks up smem item and returns pointer to it. Size of smem - * item is returned in @size. - */ -static void *qcom_smem_get(unsigned int host, unsigned int item, size_t *size) -{ - struct smem_partition_header *phdr; - size_t cacheln; - void *ptr = ERR_PTR(-ENOMEM); - - if (!__smem) - return ptr; - - if (WARN_ON(item >= __smem->item_count)) - return ERR_PTR(-EINVAL); - - if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { - phdr = __smem->partitions[host]; - cacheln = __smem->cacheline[host]; - ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); - } else if (__smem->global_partition) { - phdr = __smem->global_partition; - cacheln = __smem->global_cacheline; - ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); - } else { - ptr = qcom_smem_get_global(__smem, item, size); - } - - return ptr; - -} - -/** - * qcom_smem_get_free_space() - retrieve amount of free space in a partition - * @host: the remote processor identifying a partition, or -1 - * - * To be used by smem clients as a quick way to determine if any new - * allocations has been made. - */ -static int qcom_smem_get_free_space(unsigned int host) -{ - struct smem_partition_header *phdr; - struct smem_header *header; - unsigned int ret; - - if (!__smem) - return -ENOMEM; - - if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { - phdr = __smem->partitions[host]; - ret = le32_to_cpu(phdr->offset_free_cached) - - le32_to_cpu(phdr->offset_free_uncached); - } else if (__smem->global_partition) { - phdr = __smem->global_partition; - ret = le32_to_cpu(phdr->offset_free_cached) - - le32_to_cpu(phdr->offset_free_uncached); - } else { - header = __smem->regions[0].virt_base; - ret = le32_to_cpu(header->available); - } - - return ret; -} - -static int qcom_smem_get_sbl_version(struct qcom_smem *smem) -{ - struct smem_header *header; - __le32 *versions; - - header = smem->regions[0].virt_base; - versions = header->version; - - return le32_to_cpu(versions[SMEM_MASTER_SBL_VERSION_INDEX]); -} - -static struct smem_ptable *qcom_smem_get_ptable(struct qcom_smem *smem) -{ - struct smem_ptable *ptable; - u32 version; - - ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K; - if (memcmp(ptable->magic, SMEM_PTABLE_MAGIC, sizeof(ptable->magic))) - return ERR_PTR(-ENOENT); - - version = le32_to_cpu(ptable->version); - if (version != 1) { - dev_err(smem->dev, - "Unsupported partition header version %d\n", version); - return ERR_PTR(-EINVAL); - } - return ptable; -} - -static u32 qcom_smem_get_item_count(struct qcom_smem *smem) -{ - struct smem_ptable *ptable; - struct smem_info *info; - - ptable = qcom_smem_get_ptable(smem); - if (IS_ERR_OR_NULL(ptable)) - return SMEM_ITEM_COUNT; - - info = (struct smem_info *)&ptable->entry[ptable->num_entries]; - if (memcmp(info->magic, SMEM_INFO_MAGIC, sizeof(info->magic))) - return SMEM_ITEM_COUNT; - - return le16_to_cpu(info->num_items); -} - -static int qcom_smem_set_global_partition(struct qcom_smem *smem) -{ - struct smem_partition_header *header; - struct smem_ptable_entry *entry = NULL; - struct smem_ptable *ptable; - u32 host0, host1, size; - int i; - - ptable = qcom_smem_get_ptable(smem); - if (IS_ERR(ptable)) - return PTR_ERR(ptable); - - for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { - entry = &ptable->entry[i]; - host0 = le16_to_cpu(entry->host0); - host1 = le16_to_cpu(entry->host1); - - if (host0 == SMEM_GLOBAL_HOST && host0 == host1) - break; - } - - if (!entry) { - dev_err(smem->dev, "Missing entry for global partition\n"); - return -EINVAL; - } - - if (!le32_to_cpu(entry->offset) || !le32_to_cpu(entry->size)) { - dev_err(smem->dev, "Invalid entry for global partition\n"); - return -EINVAL; - } - - if (smem->global_partition) { - dev_err(smem->dev, "Already found the global partition\n"); - return -EINVAL; - } - - header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); - host0 = le16_to_cpu(header->host0); - host1 = le16_to_cpu(header->host1); - - if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) { - dev_err(smem->dev, "Global partition has invalid magic\n"); - return -EINVAL; - } - - if (host0 != SMEM_GLOBAL_HOST && host1 != SMEM_GLOBAL_HOST) { - dev_err(smem->dev, "Global partition hosts are invalid\n"); - return -EINVAL; - } - - if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) { - dev_err(smem->dev, "Global partition has invalid size\n"); - return -EINVAL; - } - - size = le32_to_cpu(header->offset_free_uncached); - if (size > le32_to_cpu(header->size)) { - dev_err(smem->dev, - "Global partition has invalid free pointer\n"); - return -EINVAL; - } - - smem->global_partition = header; - smem->global_cacheline = le32_to_cpu(entry->cacheline); - - return 0; -} - -static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, - unsigned int local_host) -{ - struct smem_partition_header *header; - struct smem_ptable_entry *entry; - struct smem_ptable *ptable; - unsigned int remote_host; - u32 host0, host1; - int i; - - ptable = qcom_smem_get_ptable(smem); - if (IS_ERR(ptable)) - return PTR_ERR(ptable); - - for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { - entry = &ptable->entry[i]; - host0 = le16_to_cpu(entry->host0); - host1 = le16_to_cpu(entry->host1); - - if (host0 != local_host && host1 != local_host) - continue; - - if (!le32_to_cpu(entry->offset)) - continue; - - if (!le32_to_cpu(entry->size)) - continue; - - if (host0 == local_host) - remote_host = host1; - else - remote_host = host0; - - if (remote_host >= SMEM_HOST_COUNT) { - dev_err(smem->dev, - "Invalid remote host %d\n", - remote_host); - return -EINVAL; - } - - if (smem->partitions[remote_host]) { - dev_err(smem->dev, - "Already found a partition for host %d\n", - remote_host); - return -EINVAL; - } - - header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); - host0 = le16_to_cpu(header->host0); - host1 = le16_to_cpu(header->host1); - - if (memcmp(header->magic, SMEM_PART_MAGIC, - sizeof(header->magic))) { - dev_err(smem->dev, - "Partition %d has invalid magic\n", i); - return -EINVAL; - } - - if (host0 != local_host && host1 != local_host) { - dev_err(smem->dev, - "Partition %d hosts are invalid\n", i); - return -EINVAL; - } - - if (host0 != remote_host && host1 != remote_host) { - dev_err(smem->dev, - "Partition %d hosts are invalid\n", i); - return -EINVAL; - } - - if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) { - dev_err(smem->dev, - "Partition %d has invalid size\n", i); - return -EINVAL; - } - - if (le32_to_cpu(header->offset_free_uncached) > le32_to_cpu(header->size)) { - dev_err(smem->dev, - "Partition %d has invalid free pointer\n", i); - return -EINVAL; - } - - smem->partitions[remote_host] = header; - smem->cacheline[remote_host] = le32_to_cpu(entry->cacheline); - } - - return 0; -} - -static int qcom_smem_map_memory(struct qcom_smem *smem, struct udevice *dev, - const char *name, int i) -{ - int ret; - struct ofnode_phandle_args args; - struct resource r; - - if (!dev_read_prop(dev, name, NULL)) { - dev_err(dev, "%s prop not found\n", name); - return -EINVAL; - } - - ret = dev_read_phandle_with_args(dev, name, NULL, 0, 0, &args); - if (ret) { - dev_err(dev, "%s phandle read failed\n", name); - return -EINVAL; - } - - if (!ofnode_valid(args.node)) { - dev_err(dev, "Invalid node from phandle args\n"); - return -EINVAL; - } - - ret = ofnode_read_resource(args.node, 0, &r); - if (ret) { - dev_err(dev, "Can't get mmap base address(%d)\n", ret); - return ret; - } - smem->regions[i].aux_base = (u32)r.start; - smem->regions[i].size = resource_size(&r); - smem->regions[i].virt_base = devm_ioremap(dev, r.start, resource_size(&r)); - if (!smem->regions[i].virt_base) - return -ENOMEM; - - return 0; -} - -static int qcom_smem_probe(struct udevice *dev) -{ - struct smem_header *header; - struct qcom_smem *smem; - size_t array_size; - int num_regions; - u32 version; - int ret; - fdt_addr_t addr; - fdt_size_t size; - - if (__smem) - return 0; - - num_regions = 1; - if (dev_read_prop(dev, "qcom,rpm-msg-ram", NULL)) - num_regions++; - - array_size = num_regions * sizeof(struct smem_region); - smem = devm_kzalloc(dev, sizeof(*smem) + array_size, GFP_KERNEL); - if (!smem) - return -ENOMEM; - - smem->dev = dev; - smem->num_regions = num_regions; - - addr = dev_read_addr_size(dev, &size); - if (addr == FDT_ADDR_T_NONE) { - ret = qcom_smem_map_memory(smem, dev, "memory-region", 0); - if (ret) - return ret; - } else { - smem->regions[0].aux_base = (u32)addr; - smem->regions[0].size = size; - smem->regions[0].virt_base = devm_ioremap(dev, addr, size); - if (!smem->regions[0].virt_base) - return -ENOMEM; - } - - if (num_regions > 1) { - ret = qcom_smem_map_memory(smem, dev, - "qcom,rpm-msg-ram", 1); - if (ret) - return ret; - } - - header = smem->regions[0].virt_base; - if (le32_to_cpu(header->initialized) != 1 || - le32_to_cpu(header->reserved)) { - dev_err(dev, "SMEM is not initialized by SBL\n"); - return -EINVAL; - } - - version = qcom_smem_get_sbl_version(smem); - switch (version >> 16) { - case SMEM_GLOBAL_PART_VERSION: - ret = qcom_smem_set_global_partition(smem); - if (ret < 0) - return ret; - smem->item_count = qcom_smem_get_item_count(smem); - break; - case SMEM_GLOBAL_HEAP_VERSION: - smem->item_count = SMEM_ITEM_COUNT; - break; - default: - dev_err(dev, "Unsupported SMEM version 0x%x\n", version); - return -EINVAL; - } - - ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); - if (ret < 0 && ret != -ENOENT) - return ret; - - __smem = smem; - - return 0; -} - -static int qcom_smem_remove(struct udevice *dev) -{ - __smem = NULL; - - return 0; -} - -const struct udevice_id qcom_smem_of_match[] = { - { .compatible = "qcom,smem" }, - { } -}; - -static const struct smem_ops msm_smem_ops = { - .alloc = qcom_smem_alloc, - .get = qcom_smem_get, - .get_free_space = qcom_smem_get_free_space, -}; - -U_BOOT_DRIVER(qcom_smem) = { - .name = "qcom_smem", - .id = UCLASS_SMEM, - .of_match = qcom_smem_of_match, - .ops = &msm_smem_ops, - .probe = qcom_smem_probe, - .remove = qcom_smem_remove, -}; diff --git a/drivers/smem/sandbox_smem.c b/drivers/smem/sandbox_smem.c deleted file mode 100644 index fec98e5611d6..000000000000 --- a/drivers/smem/sandbox_smem.c +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2018 Ramon Fried - */ - -#include -#include -#include -#include - -static int sandbox_smem_alloc(unsigned int host, - unsigned int item, size_t size) -{ - return 0; -} - -static void *sandbox_smem_get(unsigned int host, - unsigned int item, size_t *size) -{ - return NULL; -} - -static int sandbox_smem_get_free_space(unsigned int host) -{ - return 0; -} - -static const struct smem_ops sandbox_smem_ops = { - .alloc = sandbox_smem_alloc, - .get = sandbox_smem_get, - .get_free_space = sandbox_smem_get_free_space, -}; - -static const struct udevice_id sandbox_smem_ids[] = { - { .compatible = "sandbox,smem" }, - { } -}; - -U_BOOT_DRIVER(smem_sandbox) = { - .name = "smem_sandbox", - .id = UCLASS_SMEM, - .of_match = sandbox_smem_ids, - .ops = &sandbox_smem_ops, -}; From bbaad87a76dc12b40e3b059c051543bd8cd4247f Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:31 +0200 Subject: [PATCH 45/70] Revert "test: smem: add basic smem test" This reverts commit 20e7705764c4e5ea924f1ea54bb36ebbbeffffe7. Signed-off-by: Casey Connolly Reviewed-by: Simon Glass --- test/dm/Makefile | 1 - test/dm/smem.c | 26 -------------------------- 2 files changed, 27 deletions(-) delete mode 100644 test/dm/smem.c diff --git a/test/dm/Makefile b/test/dm/Makefile index ca2401dabc1e..5995e06f5094 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -114,7 +114,6 @@ obj-$(CONFIG_DM_SERIAL) += serial.o obj-$(CONFIG_DM_SPI_FLASH) += sf.o obj-$(CONFIG_SIMPLE_BUS) += simple-bus.o obj-$(CONFIG_SIMPLE_PM_BUS) += simple-pm-bus.o -obj-$(CONFIG_SMEM) += smem.o obj-$(CONFIG_SOC_DEVICE) += soc.o obj-$(CONFIG_SOUND) += sound.o obj-$(CONFIG_DM_SPI) += spi.o diff --git a/test/dm/smem.c b/test/dm/smem.c deleted file mode 100644 index 89e74cccc574..000000000000 --- a/test/dm/smem.c +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2018 Ramon Fried - */ - -#include -#include -#include -#include -#include - -/* Basic test of the smem uclass */ -static int dm_test_smem_base(struct unit_test_state *uts) -{ - struct udevice *dev; - size_t size; - - ut_assertok(uclass_get_device(UCLASS_SMEM, 0, &dev)); - ut_assertnonnull(dev); - ut_assertok(smem_alloc(dev, -1, 0, 16)); - ut_asserteq(0, smem_get_free_space(dev, -1)); - ut_assertnull(smem_get(dev, -1, 0, &size)); - - return 0; -} -DM_TEST(dm_test_smem_base, UTF_SCAN_PDATA | UTF_SCAN_FDT); From 7109625bf13fa3c61ec25804f3d3d32f8e919d1a Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:32 +0200 Subject: [PATCH 46/70] Revert "drivers: smem: sandbox" This reverts commit 7fd7e2cf339ea2ec570f6dae1cdfaf8e066eb4af. Signed-off-by: Casey Connolly --- arch/sandbox/dts/test.dts | 4 ---- configs/sandbox64_defconfig | 2 -- configs/sandbox_defconfig | 2 -- 3 files changed, 8 deletions(-) diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 769f43efe4d5..beba425888c5 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -1594,10 +1594,6 @@ sandbox,filepath = "scsi.img"; }; - smem@0 { - compatible = "sandbox,smem"; - }; - sound { compatible = "sandbox,sound"; cpu { diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig index 5bf6146b1d0c..3cc1e7f77f64 100644 --- a/configs/sandbox64_defconfig +++ b/configs/sandbox64_defconfig @@ -232,8 +232,6 @@ CONFIG_REMOTEPROC_SANDBOX=y CONFIG_DM_RTC=y CONFIG_RTC_RV8803=y CONFIG_SANDBOX_SERIAL=y -CONFIG_SMEM=y -CONFIG_SANDBOX_SMEM=y CONFIG_SOUND_SANDBOX=y CONFIG_SOC_DEVICE=y CONFIG_SANDBOX_SPI=y diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index ba800f7d19da..42dc7b07b117 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -325,8 +325,6 @@ CONFIG_RTC_RV8803=y CONFIG_RTC_HT1380=y CONFIG_SANDBOX_SERIAL=y CONFIG_SANDBOX_SM=y -CONFIG_SMEM=y -CONFIG_SANDBOX_SMEM=y CONFIG_SOUND_DA7219=y CONFIG_SOUND_MAX98357A=y CONFIG_SOUND_SANDBOX=y From 710ca394d37b392dbd27ea6a0bcf652cf446897b Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:33 +0200 Subject: [PATCH 47/70] mach-snapdragon: fix reserved memory carveout The memory carveout logic was fairly limited and had a few issues, rework it and teach it not to unmap regions that have a compatible property (since they may be used in U-Boot) or that don't have the no-map property. The carveout process adds ~100ms to the boot time depending on the platform. This prepares us for using SMEM as a source of truth and improving support for U-boot as a first stage bootloader since SMEMs memory map doesn't already carve out some regions like ABL does. Signed-off-by: Casey Connolly --- arch/arm/mach-snapdragon/board.c | 86 ++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-snapdragon/board.c index bbf2c2df9b4f..0602782a1081 100644 --- a/arch/arm/mach-snapdragon/board.c +++ b/arch/arm/mach-snapdragon/board.c @@ -625,25 +625,34 @@ u64 get_page_table_size(void) return SZ_1M; } +struct mem_resource_attrs { + fdt_addr_t start; + fdt_addr_t size; + u64 attrs; +}; + static int fdt_cmp_res(const void *v1, const void *v2) { - const struct fdt_resource *res1 = v1, *res2 = v2; + const struct mem_resource_attrs *res1 = v1, *res2 = v2; return res1->start - res2->start; } -#define N_RESERVED_REGIONS 32 +#define N_RESERVED_REGIONS 64 -/* Mark all no-map regions as PTE_TYPE_FAULT to prevent speculative access. +/* Map and unmap reserved memory regions as appropriate. + * Mark all no-map regions as PTE_TYPE_FAULT to prevent speculative access. * On some platforms this is enough to trigger a security violation and trap * to EL3. + * Regions that may be accessed by drivers get mapped explicitly. */ -static void carve_out_reserved_memory(void) +static void configure_reserved_memory(void) { - static struct fdt_resource res[N_RESERVED_REGIONS] = { 0 }; + static struct mem_resource_attrs res[N_RESERVED_REGIONS] = { 0 }; int parent, rmem, count, i = 0; phys_addr_t start; size_t size; + u64 attrs; /* Some reserved nodes must be carved out, as the cache-prefetcher may otherwise * attempt to access them, causing a security exception. @@ -654,12 +663,17 @@ static void carve_out_reserved_memory(void) return; } - /* Collect the reserved memory regions */ + /* Collect the reserved memory regions and appropriate attrs */ fdt_for_each_subnode(rmem, gd->fdt_blob, parent) { const fdt32_t *ptr; - int len; + attrs = PTE_TYPE_FAULT; + /* If the no-map property isn't set then the region is valid */ if (!fdt_getprop(gd->fdt_blob, rmem, "no-map", NULL)) - continue; + attrs = PTE_TYPE_VALID | PTE_BLOCK_MEMTYPE(MT_NORMAL); + /* If the compatible property is set then this region may be accessed by drivers and should + * be marked valid too. */ + if (fdt_getprop(gd->fdt_blob, rmem, "compatible", NULL)) + attrs = PTE_TYPE_VALID | PTE_BLOCK_MEMTYPE(MT_NORMAL); if (i == N_RESERVED_REGIONS) { log_err("Too many reserved regions!\n"); @@ -670,48 +684,53 @@ static void carve_out_reserved_memory(void) * fdt_get_resource() takes ~70ms on SDM845, but open-coding the happy path here * takes <1ms... Oh the woes of no dcache. */ - ptr = fdt_getprop(gd->fdt_blob, rmem, "reg", &len); + ptr = fdt_getprop(gd->fdt_blob, rmem, "reg", NULL); if (ptr) { /* Qualcomm devices use #address/size-cells = <2> but all reserved regions are within * the 32-bit address space. So we can cheat here for speed. */ res[i].start = fdt32_to_cpu(ptr[1]); - res[i].end = res[i].start + fdt32_to_cpu(ptr[3]); + res[i].size = fdt32_to_cpu(ptr[3]); + res[i].attrs = attrs; i++; } } /* Sort the reserved memory regions by address */ count = i; - qsort(res, count, sizeof(struct fdt_resource), fdt_cmp_res); + qsort(res, count, sizeof(res[0]), fdt_cmp_res); + debug("Mapping %d regions!\n", count); /* Now set the right attributes for them. Often a lot of the regions are tightly packed together - * so we can optimise the number of calls to mmu_change_region_attr() by combining adjacent + * so we can optimise the number of calls to mmu_change_region_attr_nobreak() by combining adjacent * regions. */ - start = ALIGN_DOWN(res[0].start, SZ_2M); - size = ALIGN(res[0].end - start, SZ_2M); + start = res[0].start; + size = res[0].size; + attrs = res[0].attrs; + /* For each region after the first one, either increase the `size` to eventually be mapped or + * map the region we have and start a new one, this allows us to reduce the number of calls to + * mmu_map_region(). The loop is therefore "lagging" behind by one iteration. */ for (i = 1; i <= count; i++) { - /* We ideally want to 2M align everything for more efficient pagetables, but we must avoid - * overwriting reserved memory regions which shouldn't be mapped as FAULT (like those with - * compatible properties). - * If within 2M of the previous region, bump the size to include this region. Otherwise - * start a new region. - */ - if (i == count || start + size < res[i].start - SZ_2M) { - debug(" 0x%016llx - 0x%016llx: reserved\n", - start, start + size); - mmu_change_region_attr(start, size, PTE_TYPE_FAULT); - /* If this is the final region then quit here before we index - * out of bounds... - */ + /* If i == count we are done, just map the last region. If the last region is + * too far away or the attrs don't match then map the meta-region we have and + * start a new one. */ + if (i == count || start + size < res[i].start - SZ_8K || attrs != res[i].attrs) { + debug(" 0x%016llx - 0x%016llx: %s\n", + start, start + size, attrs == PTE_TYPE_FAULT ? "FAULT" : "VALID"); + /* No need to break-before-make since dcache is disabled */ + mmu_change_region_attr_nobreak(start, size, attrs); + /* We have now mapped all the regions */ if (i == count) break; - start = ALIGN_DOWN(res[i].start, SZ_2M); - size = ALIGN(res[i].end - start, SZ_2M); + /* Start a new meta-region */ + start = res[i].start; + size = res[i].size; + attrs = res[i].attrs; } else { - /* Bump size if this region is immediately after the previous one */ - size = ALIGN(res[i].end - start, SZ_2M); + /* This region is next to (<8K) the previous one so combine them. + * Accounting for any small (<8K) gap. */ + size = (res[i].start - start) + res[i].size; } } } @@ -803,11 +822,12 @@ void enable_caches(void) gd->arch.tlb_addr = tlb_addr; gd->arch.tlb_size = tlb_size; - /* We do the carveouts only for QCS404, for now. */ + /* On some boards speculative access may trigger a NOC or XPU violation so explicitly mark reserved + * regions as inacessible (PTE_TYPE_FAULT) */ if (fdt_node_check_compatible(gd->fdt_blob, 0, "qcom,qcs404") == 0) { carveout_start = get_timer(0); /* Takes ~20-50ms on SDM845 */ - carve_out_reserved_memory(); + configure_reserved_memory(); debug("carveout time: %lums\n", get_timer(carveout_start)); } dcache_enable(); From d8bb6d7ed8a1e87e2f1dbeacf72682595f08af3a Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:34 +0200 Subject: [PATCH 48/70] soc: qcom: import smem from Linux 6.11-rc2 Import the SMEM driver from Linux. Signed-off-by: Casey Connolly Reviewed-by: Simon Glass --- drivers/soc/qcom/smem.c | 1279 ++++++++++++++++++++++++++++++++++++ include/soc/qcom/smem.h | 20 + include/soc/qcom/socinfo.h | 111 ++++ 3 files changed, 1410 insertions(+) create mode 100644 drivers/soc/qcom/smem.c create mode 100644 include/soc/qcom/smem.h create mode 100644 include/soc/qcom/socinfo.h diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c new file mode 100644 index 000000000000..8515b8ae7777 --- /dev/null +++ b/drivers/soc/qcom/smem.c @@ -0,0 +1,1279 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, Sony Mobile Communications AB. + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The Qualcomm shared memory system is a allocate only heap structure that + * consists of one of more memory areas that can be accessed by the processors + * in the SoC. + * + * All systems contains a global heap, accessible by all processors in the SoC, + * with a table of contents data structure (@smem_header) at the beginning of + * the main shared memory block. + * + * The global header contains meta data for allocations as well as a fixed list + * of 512 entries (@smem_global_entry) that can be initialized to reference + * parts of the shared memory space. + * + * + * In addition to this global heap a set of "private" heaps can be set up at + * boot time with access restrictions so that only certain processor pairs can + * access the data. + * + * These partitions are referenced from an optional partition table + * (@smem_ptable), that is found 4kB from the end of the main smem region. The + * partition table entries (@smem_ptable_entry) lists the involved processors + * (or hosts) and their location in the main shared memory region. + * + * Each partition starts with a header (@smem_partition_header) that identifies + * the partition and holds properties for the two internal memory regions. The + * two regions are cached and non-cached memory respectively. Each region + * contain a link list of allocation headers (@smem_private_entry) followed by + * their data. + * + * Items in the non-cached region are allocated from the start of the partition + * while items in the cached region are allocated from the end. The free area + * is hence the region between the cached and non-cached offsets. The header of + * cached items comes after the data. + * + * Version 12 (SMEM_GLOBAL_PART_VERSION) changes the item alloc/get procedure + * for the global heap. A new global partition is created from the global heap + * region with partition type (SMEM_GLOBAL_HOST) and the max smem item count is + * set by the bootloader. + * + * To synchronize allocations in the shared memory heaps a remote spinlock must + * be held - currently lock number 3 of the sfpb or tcsr is used for this on all + * platforms. + * + */ + +/* + * The version member of the smem header contains an array of versions for the + * various software components in the SoC. We verify that the boot loader + * version is a valid version as a sanity check. + */ +#define SMEM_MASTER_SBL_VERSION_INDEX 7 +#define SMEM_GLOBAL_HEAP_VERSION 11 +#define SMEM_GLOBAL_PART_VERSION 12 + +/* + * The first 8 items are only to be allocated by the boot loader while + * initializing the heap. + */ +#define SMEM_ITEM_LAST_FIXED 8 + +/* Highest accepted item number, for both global and private heaps */ +#define SMEM_ITEM_COUNT 512 + +/* Processor/host identifier for the application processor */ +#define SMEM_HOST_APPS 0 + +/* Processor/host identifier for the global partition */ +#define SMEM_GLOBAL_HOST 0xfffe + +/* Max number of processors/hosts in a system */ +#define SMEM_HOST_COUNT 20 + +/** + * struct smem_proc_comm - proc_comm communication struct (legacy) + * @command: current command to be executed + * @status: status of the currently requested command + * @params: parameters to the command + */ +struct smem_proc_comm { + __le32 command; + __le32 status; + __le32 params[2]; +}; + +/** + * struct smem_global_entry - entry to reference smem items on the heap + * @allocated: boolean to indicate if this entry is used + * @offset: offset to the allocated space + * @size: size of the allocated space, 8 byte aligned + * @aux_base: base address for the memory region used by this unit, or 0 for + * the default region. bits 0,1 are reserved + */ +struct smem_global_entry { + __le32 allocated; + __le32 offset; + __le32 size; + __le32 aux_base; /* bits 1:0 reserved */ +}; +#define AUX_BASE_MASK 0xfffffffc + +/** + * struct smem_header - header found in beginning of primary smem region + * @proc_comm: proc_comm communication interface (legacy) + * @version: array of versions for the various subsystems + * @initialized: boolean to indicate that smem is initialized + * @free_offset: index of the first unallocated byte in smem + * @available: number of bytes available for allocation + * @reserved: reserved field, must be 0 + * @toc: array of references to items + */ +struct smem_header { + struct smem_proc_comm proc_comm[4]; + __le32 version[32]; + __le32 initialized; + __le32 free_offset; + __le32 available; + __le32 reserved; + struct smem_global_entry toc[SMEM_ITEM_COUNT]; +}; + +/** + * struct smem_ptable_entry - one entry in the @smem_ptable list + * @offset: offset, within the main shared memory region, of the partition + * @size: size of the partition + * @flags: flags for the partition (currently unused) + * @host0: first processor/host with access to this partition + * @host1: second processor/host with access to this partition + * @cacheline: alignment for "cached" entries + * @reserved: reserved entries for later use + */ +struct smem_ptable_entry { + __le32 offset; + __le32 size; + __le32 flags; + __le16 host0; + __le16 host1; + __le32 cacheline; + __le32 reserved[7]; +}; + +/** + * struct smem_ptable - partition table for the private partitions + * @magic: magic number, must be SMEM_PTABLE_MAGIC + * @version: version of the partition table + * @num_entries: number of partitions in the table + * @reserved: for now reserved entries + * @entry: list of @smem_ptable_entry for the @num_entries partitions + */ +struct smem_ptable { + u8 magic[4]; + __le32 version; + __le32 num_entries; + __le32 reserved[5]; + struct smem_ptable_entry entry[]; +}; + +static const u8 SMEM_PTABLE_MAGIC[] = { 0x24, 0x54, 0x4f, 0x43 }; /* "$TOC" */ + +/** + * struct smem_partition_header - header of the partitions + * @magic: magic number, must be SMEM_PART_MAGIC + * @host0: first processor/host with access to this partition + * @host1: second processor/host with access to this partition + * @size: size of the partition + * @offset_free_uncached: offset to the first free byte of uncached memory in + * this partition + * @offset_free_cached: offset to the first free byte of cached memory in this + * partition + * @reserved: for now reserved entries + */ +struct smem_partition_header { + u8 magic[4]; + __le16 host0; + __le16 host1; + __le32 size; + __le32 offset_free_uncached; + __le32 offset_free_cached; + __le32 reserved[3]; +}; + +/** + * struct smem_partition - describes smem partition + * @virt_base: starting virtual address of partition + * @phys_base: starting physical address of partition + * @cacheline: alignment for "cached" entries + * @size: size of partition + */ +struct smem_partition { + void __iomem *virt_base; + phys_addr_t phys_base; + size_t cacheline; + size_t size; +}; + +static const u8 SMEM_PART_MAGIC[] = { 0x24, 0x50, 0x52, 0x54 }; + +/** + * struct smem_private_entry - header of each item in the private partition + * @canary: magic number, must be SMEM_PRIVATE_CANARY + * @item: identifying number of the smem item + * @size: size of the data, including padding bytes + * @padding_data: number of bytes of padding of data + * @padding_hdr: number of bytes of padding between the header and the data + * @reserved: for now reserved entry + */ +struct smem_private_entry { + u16 canary; /* bytes are the same so no swapping needed */ + __le16 item; + __le32 size; /* includes padding bytes */ + __le16 padding_data; + __le16 padding_hdr; + __le32 reserved; +}; +#define SMEM_PRIVATE_CANARY 0xa5a5 + +/** + * struct smem_info - smem region info located after the table of contents + * @magic: magic number, must be SMEM_INFO_MAGIC + * @size: size of the smem region + * @base_addr: base address of the smem region + * @reserved: for now reserved entry + * @num_items: highest accepted item number + */ +struct smem_info { + u8 magic[4]; + __le32 size; + __le32 base_addr; + __le32 reserved; + __le16 num_items; +}; + +static const u8 SMEM_INFO_MAGIC[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ + +/** + * struct smem_region - representation of a chunk of memory used for smem + * @aux_base: identifier of aux_mem base + * @virt_base: virtual base address of memory with this aux_mem identifier + * @size: size of the memory region + */ +struct smem_region { + phys_addr_t aux_base; + void __iomem *virt_base; + size_t size; +}; + +/** + * struct qcom_smem - device data for the smem device + * @dev: device pointer + * @hwlock: reference to a hwspinlock + * @ptable: virtual base of partition table + * @global_partition: describes for global partition when in use + * @partitions: list of partitions of current processor/host + * @item_count: max accepted item number + * @socinfo: platform device pointer + * @num_regions: number of @regions + * @regions: list of the memory regions defining the shared memory + */ +struct qcom_smem { + struct device *dev; + + struct hwspinlock *hwlock; + + u32 item_count; + struct platform_device *socinfo; + struct smem_ptable *ptable; + struct smem_partition global_partition; + struct smem_partition partitions[SMEM_HOST_COUNT]; + + unsigned num_regions; + struct smem_region regions[] __counted_by(num_regions); +}; + +static void * +phdr_to_last_uncached_entry(struct smem_partition_header *phdr) +{ + void *p = phdr; + + return p + le32_to_cpu(phdr->offset_free_uncached); +} + +static struct smem_private_entry * +phdr_to_first_cached_entry(struct smem_partition_header *phdr, + size_t cacheline) +{ + void *p = phdr; + struct smem_private_entry *e; + + return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*e), cacheline); +} + +static void * +phdr_to_last_cached_entry(struct smem_partition_header *phdr) +{ + void *p = phdr; + + return p + le32_to_cpu(phdr->offset_free_cached); +} + +static struct smem_private_entry * +phdr_to_first_uncached_entry(struct smem_partition_header *phdr) +{ + void *p = phdr; + + return p + sizeof(*phdr); +} + +static struct smem_private_entry * +uncached_entry_next(struct smem_private_entry *e) +{ + void *p = e; + + return p + sizeof(*e) + le16_to_cpu(e->padding_hdr) + + le32_to_cpu(e->size); +} + +static struct smem_private_entry * +cached_entry_next(struct smem_private_entry *e, size_t cacheline) +{ + void *p = e; + + return p - le32_to_cpu(e->size) - ALIGN(sizeof(*e), cacheline); +} + +static void *uncached_entry_to_item(struct smem_private_entry *e) +{ + void *p = e; + + return p + sizeof(*e) + le16_to_cpu(e->padding_hdr); +} + +static void *cached_entry_to_item(struct smem_private_entry *e) +{ + void *p = e; + + return p - le32_to_cpu(e->size); +} + +/* Pointer to the one and only smem handle */ +static struct qcom_smem *__smem; + +/* Timeout (ms) for the trylock of remote spinlocks */ +#define HWSPINLOCK_TIMEOUT 1000 + +/* The qcom hwspinlock id is always plus one from the smem host id */ +#define SMEM_HOST_ID_TO_HWSPINLOCK_ID(__x) ((__x) + 1) + +/** + * qcom_smem_bust_hwspin_lock_by_host() - bust the smem hwspinlock for a host + * @host: remote processor id + * + * Busts the hwspin_lock for the given smem host id. This helper is intended + * for remoteproc drivers that manage remoteprocs with an equivalent smem + * driver instance in the remote firmware. Drivers can force a release of the + * smem hwspin_lock if the rproc unexpectedly goes into a bad state. + * + * Context: Process context. + * + * Returns: 0 on success, otherwise negative errno. + */ +int qcom_smem_bust_hwspin_lock_by_host(unsigned int host) +{ + /* This function is for remote procs, so ignore SMEM_HOST_APPS */ + if (host == SMEM_HOST_APPS || host >= SMEM_HOST_COUNT) + return -EINVAL; + + return hwspin_lock_bust(__smem->hwlock, SMEM_HOST_ID_TO_HWSPINLOCK_ID(host)); +} +EXPORT_SYMBOL_GPL(qcom_smem_bust_hwspin_lock_by_host); + +/** + * qcom_smem_is_available() - Check if SMEM is available + * + * Return: true if SMEM is available, false otherwise. + */ +bool qcom_smem_is_available(void) +{ + return !!__smem; +} +EXPORT_SYMBOL_GPL(qcom_smem_is_available); + +static int qcom_smem_alloc_private(struct qcom_smem *smem, + struct smem_partition *part, + unsigned item, + size_t size) +{ + struct smem_private_entry *hdr, *end; + struct smem_partition_header *phdr; + size_t alloc_size; + void *cached; + void *p_end; + + phdr = (struct smem_partition_header __force *)part->virt_base; + p_end = (void *)phdr + part->size; + + hdr = phdr_to_first_uncached_entry(phdr); + end = phdr_to_last_uncached_entry(phdr); + cached = phdr_to_last_cached_entry(phdr); + + if (WARN_ON((void *)end > p_end || cached > p_end)) + return -EINVAL; + + while (hdr < end) { + if (hdr->canary != SMEM_PRIVATE_CANARY) + goto bad_canary; + if (le16_to_cpu(hdr->item) == item) + return -EEXIST; + + hdr = uncached_entry_next(hdr); + } + + if (WARN_ON((void *)hdr > p_end)) + return -EINVAL; + + /* Check that we don't grow into the cached region */ + alloc_size = sizeof(*hdr) + ALIGN(size, 8); + if ((void *)hdr + alloc_size > cached) { + dev_err(smem->dev, "Out of memory\n"); + return -ENOSPC; + } + + hdr->canary = SMEM_PRIVATE_CANARY; + hdr->item = cpu_to_le16(item); + hdr->size = cpu_to_le32(ALIGN(size, 8)); + hdr->padding_data = cpu_to_le16(le32_to_cpu(hdr->size) - size); + hdr->padding_hdr = 0; + + /* + * Ensure the header is written before we advance the free offset, so + * that remote processors that does not take the remote spinlock still + * gets a consistent view of the linked list. + */ + wmb(); + le32_add_cpu(&phdr->offset_free_uncached, alloc_size); + + return 0; +bad_canary: + dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", + le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); + + return -EINVAL; +} + +static int qcom_smem_alloc_global(struct qcom_smem *smem, + unsigned item, + size_t size) +{ + struct smem_global_entry *entry; + struct smem_header *header; + + header = smem->regions[0].virt_base; + entry = &header->toc[item]; + if (entry->allocated) + return -EEXIST; + + size = ALIGN(size, 8); + if (WARN_ON(size > le32_to_cpu(header->available))) + return -ENOMEM; + + entry->offset = header->free_offset; + entry->size = cpu_to_le32(size); + + /* + * Ensure the header is consistent before we mark the item allocated, + * so that remote processors will get a consistent view of the item + * even though they do not take the spinlock on read. + */ + wmb(); + entry->allocated = cpu_to_le32(1); + + le32_add_cpu(&header->free_offset, size); + le32_add_cpu(&header->available, -size); + + return 0; +} + +/** + * qcom_smem_alloc() - allocate space for a smem item + * @host: remote processor id, or -1 + * @item: smem item handle + * @size: number of bytes to be allocated + * + * Allocate space for a given smem item of size @size, given that the item is + * not yet allocated. + */ +int qcom_smem_alloc(unsigned host, unsigned item, size_t size) +{ + struct smem_partition *part; + unsigned long flags; + int ret; + + if (!__smem) + return -EPROBE_DEFER; + + if (item < SMEM_ITEM_LAST_FIXED) { + dev_err(__smem->dev, + "Rejecting allocation of static entry %d\n", item); + return -EINVAL; + } + + if (WARN_ON(item >= __smem->item_count)) + return -EINVAL; + + ret = hwspin_lock_timeout_irqsave(__smem->hwlock, + HWSPINLOCK_TIMEOUT, + &flags); + if (ret) + return ret; + + if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) { + part = &__smem->partitions[host]; + ret = qcom_smem_alloc_private(__smem, part, item, size); + } else if (__smem->global_partition.virt_base) { + part = &__smem->global_partition; + ret = qcom_smem_alloc_private(__smem, part, item, size); + } else { + ret = qcom_smem_alloc_global(__smem, item, size); + } + + hwspin_unlock_irqrestore(__smem->hwlock, &flags); + + return ret; +} +EXPORT_SYMBOL_GPL(qcom_smem_alloc); + +static void *qcom_smem_get_global(struct qcom_smem *smem, + unsigned item, + size_t *size) +{ + struct smem_header *header; + struct smem_region *region; + struct smem_global_entry *entry; + u64 entry_offset; + u32 e_size; + u32 aux_base; + unsigned i; + + header = smem->regions[0].virt_base; + entry = &header->toc[item]; + if (!entry->allocated) + return ERR_PTR(-ENXIO); + + aux_base = le32_to_cpu(entry->aux_base) & AUX_BASE_MASK; + + for (i = 0; i < smem->num_regions; i++) { + region = &smem->regions[i]; + + if ((u32)region->aux_base == aux_base || !aux_base) { + e_size = le32_to_cpu(entry->size); + entry_offset = le32_to_cpu(entry->offset); + + if (WARN_ON(e_size + entry_offset > region->size)) + return ERR_PTR(-EINVAL); + + if (size != NULL) + *size = e_size; + + return region->virt_base + entry_offset; + } + } + + return ERR_PTR(-ENOENT); +} + +static void *qcom_smem_get_private(struct qcom_smem *smem, + struct smem_partition *part, + unsigned item, + size_t *size) +{ + struct smem_private_entry *e, *end; + struct smem_partition_header *phdr; + void *item_ptr, *p_end; + u32 padding_data; + u32 e_size; + + phdr = (struct smem_partition_header __force *)part->virt_base; + p_end = (void *)phdr + part->size; + + e = phdr_to_first_uncached_entry(phdr); + end = phdr_to_last_uncached_entry(phdr); + + while (e < end) { + if (e->canary != SMEM_PRIVATE_CANARY) + goto invalid_canary; + + if (le16_to_cpu(e->item) == item) { + if (size != NULL) { + e_size = le32_to_cpu(e->size); + padding_data = le16_to_cpu(e->padding_data); + + if (WARN_ON(e_size > part->size || padding_data > e_size)) + return ERR_PTR(-EINVAL); + + *size = e_size - padding_data; + } + + item_ptr = uncached_entry_to_item(e); + if (WARN_ON(item_ptr > p_end)) + return ERR_PTR(-EINVAL); + + return item_ptr; + } + + e = uncached_entry_next(e); + } + + if (WARN_ON((void *)e > p_end)) + return ERR_PTR(-EINVAL); + + /* Item was not found in the uncached list, search the cached list */ + + e = phdr_to_first_cached_entry(phdr, part->cacheline); + end = phdr_to_last_cached_entry(phdr); + + if (WARN_ON((void *)e < (void *)phdr || (void *)end > p_end)) + return ERR_PTR(-EINVAL); + + while (e > end) { + if (e->canary != SMEM_PRIVATE_CANARY) + goto invalid_canary; + + if (le16_to_cpu(e->item) == item) { + if (size != NULL) { + e_size = le32_to_cpu(e->size); + padding_data = le16_to_cpu(e->padding_data); + + if (WARN_ON(e_size > part->size || padding_data > e_size)) + return ERR_PTR(-EINVAL); + + *size = e_size - padding_data; + } + + item_ptr = cached_entry_to_item(e); + if (WARN_ON(item_ptr < (void *)phdr)) + return ERR_PTR(-EINVAL); + + return item_ptr; + } + + e = cached_entry_next(e, part->cacheline); + } + + if (WARN_ON((void *)e < (void *)phdr)) + return ERR_PTR(-EINVAL); + + return ERR_PTR(-ENOENT); + +invalid_canary: + dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", + le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); + + return ERR_PTR(-EINVAL); +} + +/** + * qcom_smem_get() - resolve ptr of size of a smem item + * @host: the remote processor, or -1 + * @item: smem item handle + * @size: pointer to be filled out with size of the item + * + * Looks up smem item and returns pointer to it. Size of smem + * item is returned in @size. + */ +void *qcom_smem_get(unsigned host, unsigned item, size_t *size) +{ + struct smem_partition *part; + void *ptr = ERR_PTR(-EPROBE_DEFER); + + if (!__smem) + return ptr; + + if (WARN_ON(item >= __smem->item_count)) + return ERR_PTR(-EINVAL); + + if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) { + part = &__smem->partitions[host]; + ptr = qcom_smem_get_private(__smem, part, item, size); + } else if (__smem->global_partition.virt_base) { + part = &__smem->global_partition; + ptr = qcom_smem_get_private(__smem, part, item, size); + } else { + ptr = qcom_smem_get_global(__smem, item, size); + } + + return ptr; +} +EXPORT_SYMBOL_GPL(qcom_smem_get); + +/** + * qcom_smem_get_free_space() - retrieve amount of free space in a partition + * @host: the remote processor identifying a partition, or -1 + * + * To be used by smem clients as a quick way to determine if any new + * allocations has been made. + */ +int qcom_smem_get_free_space(unsigned host) +{ + struct smem_partition *part; + struct smem_partition_header *phdr; + struct smem_header *header; + unsigned ret; + + if (!__smem) + return -EPROBE_DEFER; + + if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) { + part = &__smem->partitions[host]; + phdr = part->virt_base; + ret = le32_to_cpu(phdr->offset_free_cached) - + le32_to_cpu(phdr->offset_free_uncached); + + if (ret > le32_to_cpu(part->size)) + return -EINVAL; + } else if (__smem->global_partition.virt_base) { + part = &__smem->global_partition; + phdr = part->virt_base; + ret = le32_to_cpu(phdr->offset_free_cached) - + le32_to_cpu(phdr->offset_free_uncached); + + if (ret > le32_to_cpu(part->size)) + return -EINVAL; + } else { + header = __smem->regions[0].virt_base; + ret = le32_to_cpu(header->available); + + if (ret > __smem->regions[0].size) + return -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(qcom_smem_get_free_space); + +static bool addr_in_range(void __iomem *base, size_t size, void *addr) +{ + return base && ((void __iomem *)addr >= base && (void __iomem *)addr < base + size); +} + +/** + * qcom_smem_virt_to_phys() - return the physical address associated + * with an smem item pointer (previously returned by qcom_smem_get() + * @p: the virtual address to convert + * + * Returns 0 if the pointer provided is not within any smem region. + */ +phys_addr_t qcom_smem_virt_to_phys(void *p) +{ + struct smem_partition *part; + struct smem_region *area; + u64 offset; + u32 i; + + for (i = 0; i < SMEM_HOST_COUNT; i++) { + part = &__smem->partitions[i]; + + if (addr_in_range(part->virt_base, part->size, p)) { + offset = p - part->virt_base; + + return (phys_addr_t)part->phys_base + offset; + } + } + + part = &__smem->global_partition; + + if (addr_in_range(part->virt_base, part->size, p)) { + offset = p - part->virt_base; + + return (phys_addr_t)part->phys_base + offset; + } + + for (i = 0; i < __smem->num_regions; i++) { + area = &__smem->regions[i]; + + if (addr_in_range(area->virt_base, area->size, p)) { + offset = p - area->virt_base; + + return (phys_addr_t)area->aux_base + offset; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_smem_virt_to_phys); + +/** + * qcom_smem_get_soc_id() - return the SoC ID + * @id: On success, we return the SoC ID here. + * + * Look up SoC ID from HW/SW build ID and return it. + * + * Return: 0 on success, negative errno on failure. + */ +int qcom_smem_get_soc_id(u32 *id) +{ + struct socinfo *info; + + info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID, NULL); + if (IS_ERR(info)) + return PTR_ERR(info); + + *id = __le32_to_cpu(info->id); + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_smem_get_soc_id); + +/** + * qcom_smem_get_feature_code() - return the feature code + * @code: On success, return the feature code here. + * + * Look up the feature code identifier from SMEM and return it. + * + * Return: 0 on success, negative errno on failure. + */ +int qcom_smem_get_feature_code(u32 *code) +{ + struct socinfo *info; + u32 raw_code; + + info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID, NULL); + if (IS_ERR(info)) + return PTR_ERR(info); + + /* This only makes sense for socinfo >= 16 */ + if (__le32_to_cpu(info->fmt) < SOCINFO_VERSION(0, 16)) + return -EOPNOTSUPP; + + raw_code = __le32_to_cpu(info->feature_code); + + /* Ensure the value makes sense */ + if (raw_code > SOCINFO_FC_INT_MAX) + raw_code = SOCINFO_FC_UNKNOWN; + + *code = raw_code; + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_smem_get_feature_code); + +static int qcom_smem_get_sbl_version(struct qcom_smem *smem) +{ + struct smem_header *header; + __le32 *versions; + + header = smem->regions[0].virt_base; + versions = header->version; + + return le32_to_cpu(versions[SMEM_MASTER_SBL_VERSION_INDEX]); +} + +static struct smem_ptable *qcom_smem_get_ptable(struct qcom_smem *smem) +{ + struct smem_ptable *ptable; + u32 version; + + ptable = smem->ptable; + if (memcmp(ptable->magic, SMEM_PTABLE_MAGIC, sizeof(ptable->magic))) + return ERR_PTR(-ENOENT); + + version = le32_to_cpu(ptable->version); + if (version != 1) { + dev_err(smem->dev, + "Unsupported partition header version %d\n", version); + return ERR_PTR(-EINVAL); + } + return ptable; +} + +static u32 qcom_smem_get_item_count(struct qcom_smem *smem) +{ + struct smem_ptable *ptable; + struct smem_info *info; + + ptable = qcom_smem_get_ptable(smem); + if (IS_ERR_OR_NULL(ptable)) + return SMEM_ITEM_COUNT; + + info = (struct smem_info *)&ptable->entry[ptable->num_entries]; + if (memcmp(info->magic, SMEM_INFO_MAGIC, sizeof(info->magic))) + return SMEM_ITEM_COUNT; + + return le16_to_cpu(info->num_items); +} + +/* + * Validate the partition header for a partition whose partition + * table entry is supplied. Returns a pointer to its header if + * valid, or a null pointer otherwise. + */ +static struct smem_partition_header * +qcom_smem_partition_header(struct qcom_smem *smem, + struct smem_ptable_entry *entry, u16 host0, u16 host1) +{ + struct smem_partition_header *header; + u32 phys_addr; + u32 size; + + phys_addr = smem->regions[0].aux_base + le32_to_cpu(entry->offset); + header = devm_ioremap_wc(smem->dev, phys_addr, le32_to_cpu(entry->size)); + + if (!header) + return NULL; + + if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) { + dev_err(smem->dev, "bad partition magic %4ph\n", header->magic); + return NULL; + } + + if (host0 != le16_to_cpu(header->host0)) { + dev_err(smem->dev, "bad host0 (%hu != %hu)\n", + host0, le16_to_cpu(header->host0)); + return NULL; + } + if (host1 != le16_to_cpu(header->host1)) { + dev_err(smem->dev, "bad host1 (%hu != %hu)\n", + host1, le16_to_cpu(header->host1)); + return NULL; + } + + size = le32_to_cpu(header->size); + if (size != le32_to_cpu(entry->size)) { + dev_err(smem->dev, "bad partition size (%u != %u)\n", + size, le32_to_cpu(entry->size)); + return NULL; + } + + if (le32_to_cpu(header->offset_free_uncached) > size) { + dev_err(smem->dev, "bad partition free uncached (%u > %u)\n", + le32_to_cpu(header->offset_free_uncached), size); + return NULL; + } + + return header; +} + +static int qcom_smem_set_global_partition(struct qcom_smem *smem) +{ + struct smem_partition_header *header; + struct smem_ptable_entry *entry; + struct smem_ptable *ptable; + bool found = false; + int i; + + if (smem->global_partition.virt_base) { + dev_err(smem->dev, "Already found the global partition\n"); + return -EINVAL; + } + + ptable = qcom_smem_get_ptable(smem); + if (IS_ERR(ptable)) + return PTR_ERR(ptable); + + for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { + entry = &ptable->entry[i]; + if (!le32_to_cpu(entry->offset)) + continue; + if (!le32_to_cpu(entry->size)) + continue; + + if (le16_to_cpu(entry->host0) != SMEM_GLOBAL_HOST) + continue; + + if (le16_to_cpu(entry->host1) == SMEM_GLOBAL_HOST) { + found = true; + break; + } + } + + if (!found) { + dev_err(smem->dev, "Missing entry for global partition\n"); + return -EINVAL; + } + + header = qcom_smem_partition_header(smem, entry, + SMEM_GLOBAL_HOST, SMEM_GLOBAL_HOST); + if (!header) + return -EINVAL; + + smem->global_partition.virt_base = (void __iomem *)header; + smem->global_partition.phys_base = smem->regions[0].aux_base + + le32_to_cpu(entry->offset); + smem->global_partition.size = le32_to_cpu(entry->size); + smem->global_partition.cacheline = le32_to_cpu(entry->cacheline); + + return 0; +} + +static int +qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host) +{ + struct smem_partition_header *header; + struct smem_ptable_entry *entry; + struct smem_ptable *ptable; + u16 remote_host; + u16 host0, host1; + int i; + + ptable = qcom_smem_get_ptable(smem); + if (IS_ERR(ptable)) + return PTR_ERR(ptable); + + for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { + entry = &ptable->entry[i]; + if (!le32_to_cpu(entry->offset)) + continue; + if (!le32_to_cpu(entry->size)) + continue; + + host0 = le16_to_cpu(entry->host0); + host1 = le16_to_cpu(entry->host1); + if (host0 == local_host) + remote_host = host1; + else if (host1 == local_host) + remote_host = host0; + else + continue; + + if (remote_host >= SMEM_HOST_COUNT) { + dev_err(smem->dev, "bad host %u\n", remote_host); + return -EINVAL; + } + + if (smem->partitions[remote_host].virt_base) { + dev_err(smem->dev, "duplicate host %u\n", remote_host); + return -EINVAL; + } + + header = qcom_smem_partition_header(smem, entry, host0, host1); + if (!header) + return -EINVAL; + + smem->partitions[remote_host].virt_base = (void __iomem *)header; + smem->partitions[remote_host].phys_base = smem->regions[0].aux_base + + le32_to_cpu(entry->offset); + smem->partitions[remote_host].size = le32_to_cpu(entry->size); + smem->partitions[remote_host].cacheline = le32_to_cpu(entry->cacheline); + } + + return 0; +} + +static int qcom_smem_map_toc(struct qcom_smem *smem, struct smem_region *region) +{ + u32 ptable_start; + + /* map starting 4K for smem header */ + region->virt_base = devm_ioremap_wc(smem->dev, region->aux_base, SZ_4K); + ptable_start = region->aux_base + region->size - SZ_4K; + /* map last 4k for toc */ + smem->ptable = devm_ioremap_wc(smem->dev, ptable_start, SZ_4K); + + if (!region->virt_base || !smem->ptable) + return -ENOMEM; + + return 0; +} + +static int qcom_smem_map_global(struct qcom_smem *smem, u32 size) +{ + u32 phys_addr; + + phys_addr = smem->regions[0].aux_base; + + smem->regions[0].size = size; + smem->regions[0].virt_base = devm_ioremap_wc(smem->dev, phys_addr, size); + + if (!smem->regions[0].virt_base) + return -ENOMEM; + + return 0; +} + +static int qcom_smem_resolve_mem(struct qcom_smem *smem, const char *name, + struct smem_region *region) +{ + struct device *dev = smem->dev; + struct device_node *np; + struct resource r; + int ret; + + np = of_parse_phandle(dev->of_node, name, 0); + if (!np) { + dev_err(dev, "No %s specified\n", name); + return -EINVAL; + } + + ret = of_address_to_resource(np, 0, &r); + of_node_put(np); + if (ret) + return ret; + + region->aux_base = r.start; + region->size = resource_size(&r); + + return 0; +} + +static int qcom_smem_probe(struct platform_device *pdev) +{ + struct smem_header *header; + struct reserved_mem *rmem; + struct qcom_smem *smem; + unsigned long flags; + int num_regions; + int hwlock_id; + u32 version; + u32 size; + int ret; + int i; + + if (__smem) + return 0; + + num_regions = 1; + if (of_property_present(pdev->dev.of_node, "qcom,rpm-msg-ram")) + num_regions++; + + smem = devm_kzalloc(&pdev->dev, struct_size(smem, regions, num_regions), + GFP_KERNEL); + if (!smem) + return -ENOMEM; + + smem->dev = &pdev->dev; + smem->num_regions = num_regions; + + rmem = of_reserved_mem_lookup(pdev->dev.of_node); + if (rmem) { + smem->regions[0].aux_base = rmem->base; + smem->regions[0].size = rmem->size; + } else { + /* + * Fall back to the memory-region reference, if we're not a + * reserved-memory node. + */ + ret = qcom_smem_resolve_mem(smem, "memory-region", &smem->regions[0]); + if (ret) + return ret; + } + + if (num_regions > 1) { + ret = qcom_smem_resolve_mem(smem, "qcom,rpm-msg-ram", &smem->regions[1]); + if (ret) + return ret; + } + + + ret = qcom_smem_map_toc(smem, &smem->regions[0]); + if (ret) + return ret; + + for (i = 1; i < num_regions; i++) { + smem->regions[i].virt_base = devm_ioremap_wc(&pdev->dev, + smem->regions[i].aux_base, + smem->regions[i].size); + if (!smem->regions[i].virt_base) { + dev_err(&pdev->dev, "failed to remap %pa\n", &smem->regions[i].aux_base); + return -ENOMEM; + } + } + + header = smem->regions[0].virt_base; + if (le32_to_cpu(header->initialized) != 1 || + le32_to_cpu(header->reserved)) { + dev_err(&pdev->dev, "SMEM is not initialized by SBL\n"); + return -EINVAL; + } + + hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); + if (hwlock_id < 0) { + if (hwlock_id != -EPROBE_DEFER) + dev_err(&pdev->dev, "failed to retrieve hwlock\n"); + return hwlock_id; + } + + smem->hwlock = hwspin_lock_request_specific(hwlock_id); + if (!smem->hwlock) + return -ENXIO; + + ret = hwspin_lock_timeout_irqsave(smem->hwlock, HWSPINLOCK_TIMEOUT, &flags); + if (ret) + return ret; + size = readl_relaxed(&header->available) + readl_relaxed(&header->free_offset); + hwspin_unlock_irqrestore(smem->hwlock, &flags); + + version = qcom_smem_get_sbl_version(smem); + /* + * smem header mapping is required only in heap version scheme, so unmap + * it here. It will be remapped in qcom_smem_map_global() when whole + * partition is mapped again. + */ + devm_iounmap(smem->dev, smem->regions[0].virt_base); + switch (version >> 16) { + case SMEM_GLOBAL_PART_VERSION: + ret = qcom_smem_set_global_partition(smem); + if (ret < 0) + return ret; + smem->item_count = qcom_smem_get_item_count(smem); + break; + case SMEM_GLOBAL_HEAP_VERSION: + qcom_smem_map_global(smem, size); + smem->item_count = SMEM_ITEM_COUNT; + break; + default: + dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version); + return -EINVAL; + } + + BUILD_BUG_ON(SMEM_HOST_APPS >= SMEM_HOST_COUNT); + ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); + if (ret < 0 && ret != -ENOENT) + return ret; + + __smem = smem; + + smem->socinfo = platform_device_register_data(&pdev->dev, "qcom-socinfo", + PLATFORM_DEVID_NONE, NULL, + 0); + if (IS_ERR(smem->socinfo)) + dev_dbg(&pdev->dev, "failed to register socinfo device\n"); + + return 0; +} + +static void qcom_smem_remove(struct platform_device *pdev) +{ + platform_device_unregister(__smem->socinfo); + + hwspin_lock_free(__smem->hwlock); + __smem = NULL; +} + +static const struct of_device_id qcom_smem_of_match[] = { + { .compatible = "qcom,smem" }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_smem_of_match); + +static struct platform_driver qcom_smem_driver = { + .probe = qcom_smem_probe, + .remove_new = qcom_smem_remove, + .driver = { + .name = "qcom-smem", + .of_match_table = qcom_smem_of_match, + .suppress_bind_attrs = true, + }, +}; + +static int __init qcom_smem_init(void) +{ + return platform_driver_register(&qcom_smem_driver); +} +arch_initcall(qcom_smem_init); + +static void __exit qcom_smem_exit(void) +{ + platform_driver_unregister(&qcom_smem_driver); +} +module_exit(qcom_smem_exit) + +MODULE_AUTHOR("Bjorn Andersson "); +MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/include/soc/qcom/smem.h b/include/soc/qcom/smem.h new file mode 100644 index 000000000000..f946e3beca21 --- /dev/null +++ b/include/soc/qcom/smem.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __QCOM_SMEM_H__ +#define __QCOM_SMEM_H__ + +#define QCOM_SMEM_HOST_ANY -1 + +bool qcom_smem_is_available(void); +int qcom_smem_alloc(unsigned host, unsigned item, size_t size); +void *qcom_smem_get(unsigned host, unsigned item, size_t *size); + +int qcom_smem_get_free_space(unsigned host); + +phys_addr_t qcom_smem_virt_to_phys(void *p); + +int qcom_smem_get_soc_id(u32 *id); +int qcom_smem_get_feature_code(u32 *code); + +int qcom_smem_bust_hwspin_lock_by_host(unsigned int host); + +#endif diff --git a/include/soc/qcom/socinfo.h b/include/soc/qcom/socinfo.h new file mode 100644 index 000000000000..608950443eee --- /dev/null +++ b/include/soc/qcom/socinfo.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __QCOM_SOCINFO_H__ +#define __QCOM_SOCINFO_H__ + +#include + +/* + * SMEM item id, used to acquire handles to respective + * SMEM region. + */ +#define SMEM_HW_SW_BUILD_ID 137 + +#define SMEM_SOCINFO_BUILD_ID_LENGTH 32 +#define SMEM_SOCINFO_CHIP_ID_LENGTH 32 + +/* + * SoC version type with major number in the upper 16 bits and minor + * number in the lower 16 bits. + */ +#define SOCINFO_MAJOR(ver) (((ver) >> 16) & 0xffff) +#define SOCINFO_MINOR(ver) ((ver) & 0xffff) +#define SOCINFO_VERSION(maj, min) ((((maj) & 0xffff) << 16)|((min) & 0xffff)) + +/* Socinfo SMEM item structure */ +struct socinfo { + __le32 fmt; + __le32 id; + __le32 ver; + char build_id[SMEM_SOCINFO_BUILD_ID_LENGTH]; + /* Version 2 */ + __le32 raw_id; + __le32 raw_ver; + /* Version 3 */ + __le32 hw_plat; + /* Version 4 */ + __le32 plat_ver; + /* Version 5 */ + __le32 accessory_chip; + /* Version 6 */ + __le32 hw_plat_subtype; + /* Version 7 */ + __le32 pmic_model; + __le32 pmic_die_rev; + /* Version 8 */ + __le32 pmic_model_1; + __le32 pmic_die_rev_1; + __le32 pmic_model_2; + __le32 pmic_die_rev_2; + /* Version 9 */ + __le32 foundry_id; + /* Version 10 */ + __le32 serial_num; + /* Version 11 */ + __le32 num_pmics; + __le32 pmic_array_offset; + /* Version 12 */ + __le32 chip_family; + __le32 raw_device_family; + __le32 raw_device_num; + /* Version 13 */ + __le32 nproduct_id; + char chip_id[SMEM_SOCINFO_CHIP_ID_LENGTH]; + /* Version 14 */ + __le32 num_clusters; + __le32 ncluster_array_offset; + __le32 num_subset_parts; + __le32 nsubset_parts_array_offset; + /* Version 15 */ + __le32 nmodem_supported; + /* Version 16 */ + __le32 feature_code; + __le32 pcode; + __le32 npartnamemap_offset; + __le32 nnum_partname_mapping; + /* Version 17 */ + __le32 oem_variant; + /* Version 18 */ + __le32 num_kvps; + __le32 kvps_offset; + /* Version 19 */ + __le32 num_func_clusters; + __le32 boot_cluster; + __le32 boot_core; +}; + +/* Internal feature codes */ +enum qcom_socinfo_feature_code { + /* External feature codes */ + SOCINFO_FC_UNKNOWN = 0x0, + SOCINFO_FC_AA, + SOCINFO_FC_AB, + SOCINFO_FC_AC, + SOCINFO_FC_AD, + SOCINFO_FC_AE, + SOCINFO_FC_AF, + SOCINFO_FC_AG, + SOCINFO_FC_AH, +}; + +/* Internal feature codes */ +/* Valid values: 0 <= n <= 0xf */ +#define SOCINFO_FC_Yn(n) (0xf1 + (n)) +#define SOCINFO_FC_INT_MAX SOCINFO_FC_Yn(0xf) + +/* Product codes */ +#define SOCINFO_PC_UNKNOWN 0 +#define SOCINFO_PCn(n) ((n) + 1) +#define SOCINFO_PC_RESERVE (BIT(31) - 1) + +#endif From 0ddccab78302cffa8644618fd09c17629e2e61c4 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:35 +0200 Subject: [PATCH 49/70] soc: qcom: smem: adjust headers for U-Boot Drop Linux headers for the U-Boot ones. Signed-off-by: Casey Connolly Reviewed-by: Simon Glass --- drivers/soc/qcom/smem.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 8515b8ae7777..7143856e85c3 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -4,17 +4,15 @@ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. */ -#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include +#include +#include /* * The Qualcomm shared memory system is a allocate only heap structure that From ddad4bdaa4069a8499875ebd8a8dc7ddfe15c117 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:36 +0200 Subject: [PATCH 50/70] soc: qcom: smem: adjust for U-Boot Port over the smem code to U-Boot and ensure the smem region gets mapped after dcache is enabled. Signed-off-by: Casey Connolly --- drivers/soc/qcom/smem.c | 397 ++++++++++------------------------------ include/soc/qcom/smem.h | 9 +- 2 files changed, 103 insertions(+), 303 deletions(-) diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 7143856e85c3..96288d7bcdef 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -4,6 +4,9 @@ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. */ +#define pr_fmt(fmt) "smem: " fmt + +#include #include #include #include @@ -12,7 +15,6 @@ #include #include #include -#include /* * The Qualcomm shared memory system is a allocate only heap structure that @@ -247,6 +249,8 @@ struct smem_info { static const u8 SMEM_INFO_MAGIC[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ +#define SMEM_MAX_REGIONS 2 + /** * struct smem_region - representation of a chunk of memory used for smem * @aux_base: identifier of aux_mem base @@ -261,31 +265,29 @@ struct smem_region { /** * struct qcom_smem - device data for the smem device - * @dev: device pointer - * @hwlock: reference to a hwspinlock * @ptable: virtual base of partition table * @global_partition: describes for global partition when in use * @partitions: list of partitions of current processor/host * @item_count: max accepted item number - * @socinfo: platform device pointer * @num_regions: number of @regions * @regions: list of the memory regions defining the shared memory */ struct qcom_smem { - struct device *dev; - - struct hwspinlock *hwlock; - u32 item_count; - struct platform_device *socinfo; struct smem_ptable *ptable; struct smem_partition global_partition; struct smem_partition partitions[SMEM_HOST_COUNT]; unsigned num_regions; - struct smem_region regions[] __counted_by(num_regions); + struct smem_region regions[SMEM_MAX_REGIONS]; }; +/* Pre-allocated storage for SMEM */ +static struct qcom_smem __smem __section(".data") = { 0 }; + +/* Pointer to the one and only smem handle */ +static struct qcom_smem *smem __section(".data") = NULL; + static void * phdr_to_last_uncached_entry(struct smem_partition_header *phdr) { @@ -351,38 +353,6 @@ static void *cached_entry_to_item(struct smem_private_entry *e) return p - le32_to_cpu(e->size); } -/* Pointer to the one and only smem handle */ -static struct qcom_smem *__smem; - -/* Timeout (ms) for the trylock of remote spinlocks */ -#define HWSPINLOCK_TIMEOUT 1000 - -/* The qcom hwspinlock id is always plus one from the smem host id */ -#define SMEM_HOST_ID_TO_HWSPINLOCK_ID(__x) ((__x) + 1) - -/** - * qcom_smem_bust_hwspin_lock_by_host() - bust the smem hwspinlock for a host - * @host: remote processor id - * - * Busts the hwspin_lock for the given smem host id. This helper is intended - * for remoteproc drivers that manage remoteprocs with an equivalent smem - * driver instance in the remote firmware. Drivers can force a release of the - * smem hwspin_lock if the rproc unexpectedly goes into a bad state. - * - * Context: Process context. - * - * Returns: 0 on success, otherwise negative errno. - */ -int qcom_smem_bust_hwspin_lock_by_host(unsigned int host) -{ - /* This function is for remote procs, so ignore SMEM_HOST_APPS */ - if (host == SMEM_HOST_APPS || host >= SMEM_HOST_COUNT) - return -EINVAL; - - return hwspin_lock_bust(__smem->hwlock, SMEM_HOST_ID_TO_HWSPINLOCK_ID(host)); -} -EXPORT_SYMBOL_GPL(qcom_smem_bust_hwspin_lock_by_host); - /** * qcom_smem_is_available() - Check if SMEM is available * @@ -390,7 +360,7 @@ EXPORT_SYMBOL_GPL(qcom_smem_bust_hwspin_lock_by_host); */ bool qcom_smem_is_available(void) { - return !!__smem; + return !!smem; } EXPORT_SYMBOL_GPL(qcom_smem_is_available); @@ -430,7 +400,7 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, /* Check that we don't grow into the cached region */ alloc_size = sizeof(*hdr) + ALIGN(size, 8); if ((void *)hdr + alloc_size > cached) { - dev_err(smem->dev, "Out of memory\n"); + log_err("Out of memory\n"); return -ENOSPC; } @@ -450,7 +420,7 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, return 0; bad_canary: - dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", + log_err("Found invalid canary in hosts %hu:%hu partition\n", le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); return -EINVAL; @@ -501,39 +471,29 @@ static int qcom_smem_alloc_global(struct qcom_smem *smem, int qcom_smem_alloc(unsigned host, unsigned item, size_t size) { struct smem_partition *part; - unsigned long flags; int ret; - if (!__smem) + if (!smem) return -EPROBE_DEFER; if (item < SMEM_ITEM_LAST_FIXED) { - dev_err(__smem->dev, - "Rejecting allocation of static entry %d\n", item); + log_err("Rejecting allocation of static entry %d\n", item); return -EINVAL; } - if (WARN_ON(item >= __smem->item_count)) + if (WARN_ON(item >= smem->item_count)) return -EINVAL; - ret = hwspin_lock_timeout_irqsave(__smem->hwlock, - HWSPINLOCK_TIMEOUT, - &flags); - if (ret) - return ret; - - if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) { - part = &__smem->partitions[host]; - ret = qcom_smem_alloc_private(__smem, part, item, size); - } else if (__smem->global_partition.virt_base) { - part = &__smem->global_partition; - ret = qcom_smem_alloc_private(__smem, part, item, size); + if (host < SMEM_HOST_COUNT && smem->partitions[host].virt_base) { + part = &smem->partitions[host]; + ret = qcom_smem_alloc_private(smem, part, item, size); + } else if (smem->global_partition.virt_base) { + part = &smem->global_partition; + ret = qcom_smem_alloc_private(smem, part, item, size); } else { - ret = qcom_smem_alloc_global(__smem, item, size); + ret = qcom_smem_alloc_global(smem, item, size); } - hwspin_unlock_irqrestore(__smem->hwlock, &flags); - return ret; } EXPORT_SYMBOL_GPL(qcom_smem_alloc); @@ -661,7 +621,7 @@ static void *qcom_smem_get_private(struct qcom_smem *smem, return ERR_PTR(-ENOENT); invalid_canary: - dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", + log_err("Found invalid canary in hosts %hu:%hu partition\n", le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); return ERR_PTR(-EINVAL); @@ -681,20 +641,20 @@ void *qcom_smem_get(unsigned host, unsigned item, size_t *size) struct smem_partition *part; void *ptr = ERR_PTR(-EPROBE_DEFER); - if (!__smem) + if (!smem) return ptr; - if (WARN_ON(item >= __smem->item_count)) + if (WARN_ON(item >= smem->item_count)) return ERR_PTR(-EINVAL); - if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) { - part = &__smem->partitions[host]; - ptr = qcom_smem_get_private(__smem, part, item, size); - } else if (__smem->global_partition.virt_base) { - part = &__smem->global_partition; - ptr = qcom_smem_get_private(__smem, part, item, size); + if (host < SMEM_HOST_COUNT && smem->partitions[host].virt_base) { + part = &smem->partitions[host]; + ptr = qcom_smem_get_private(smem, part, item, size); + } else if (smem->global_partition.virt_base) { + part = &smem->global_partition; + ptr = qcom_smem_get_private(smem, part, item, size); } else { - ptr = qcom_smem_get_global(__smem, item, size); + ptr = qcom_smem_get_global(smem, item, size); } return ptr; @@ -715,19 +675,19 @@ int qcom_smem_get_free_space(unsigned host) struct smem_header *header; unsigned ret; - if (!__smem) + if (!smem) return -EPROBE_DEFER; - if (host < SMEM_HOST_COUNT && __smem->partitions[host].virt_base) { - part = &__smem->partitions[host]; + if (host < SMEM_HOST_COUNT && smem->partitions[host].virt_base) { + part = &smem->partitions[host]; phdr = part->virt_base; ret = le32_to_cpu(phdr->offset_free_cached) - le32_to_cpu(phdr->offset_free_uncached); if (ret > le32_to_cpu(part->size)) return -EINVAL; - } else if (__smem->global_partition.virt_base) { - part = &__smem->global_partition; + } else if (smem->global_partition.virt_base) { + part = &smem->global_partition; phdr = part->virt_base; ret = le32_to_cpu(phdr->offset_free_cached) - le32_to_cpu(phdr->offset_free_uncached); @@ -735,10 +695,10 @@ int qcom_smem_get_free_space(unsigned host) if (ret > le32_to_cpu(part->size)) return -EINVAL; } else { - header = __smem->regions[0].virt_base; + header = smem->regions[0].virt_base; ret = le32_to_cpu(header->available); - if (ret > __smem->regions[0].size) + if (ret > smem->regions[0].size) return -EINVAL; } @@ -766,7 +726,7 @@ phys_addr_t qcom_smem_virt_to_phys(void *p) u32 i; for (i = 0; i < SMEM_HOST_COUNT; i++) { - part = &__smem->partitions[i]; + part = &smem->partitions[i]; if (addr_in_range(part->virt_base, part->size, p)) { offset = p - part->virt_base; @@ -775,7 +735,7 @@ phys_addr_t qcom_smem_virt_to_phys(void *p) } } - part = &__smem->global_partition; + part = &smem->global_partition; if (addr_in_range(part->virt_base, part->size, p)) { offset = p - part->virt_base; @@ -783,8 +743,8 @@ phys_addr_t qcom_smem_virt_to_phys(void *p) return (phys_addr_t)part->phys_base + offset; } - for (i = 0; i < __smem->num_regions; i++) { - area = &__smem->regions[i]; + for (i = 0; i < smem->num_regions; i++) { + area = &smem->regions[i]; if (addr_in_range(area->virt_base, area->size, p)) { offset = p - area->virt_base; @@ -797,61 +757,6 @@ phys_addr_t qcom_smem_virt_to_phys(void *p) } EXPORT_SYMBOL_GPL(qcom_smem_virt_to_phys); -/** - * qcom_smem_get_soc_id() - return the SoC ID - * @id: On success, we return the SoC ID here. - * - * Look up SoC ID from HW/SW build ID and return it. - * - * Return: 0 on success, negative errno on failure. - */ -int qcom_smem_get_soc_id(u32 *id) -{ - struct socinfo *info; - - info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID, NULL); - if (IS_ERR(info)) - return PTR_ERR(info); - - *id = __le32_to_cpu(info->id); - - return 0; -} -EXPORT_SYMBOL_GPL(qcom_smem_get_soc_id); - -/** - * qcom_smem_get_feature_code() - return the feature code - * @code: On success, return the feature code here. - * - * Look up the feature code identifier from SMEM and return it. - * - * Return: 0 on success, negative errno on failure. - */ -int qcom_smem_get_feature_code(u32 *code) -{ - struct socinfo *info; - u32 raw_code; - - info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID, NULL); - if (IS_ERR(info)) - return PTR_ERR(info); - - /* This only makes sense for socinfo >= 16 */ - if (__le32_to_cpu(info->fmt) < SOCINFO_VERSION(0, 16)) - return -EOPNOTSUPP; - - raw_code = __le32_to_cpu(info->feature_code); - - /* Ensure the value makes sense */ - if (raw_code > SOCINFO_FC_INT_MAX) - raw_code = SOCINFO_FC_UNKNOWN; - - *code = raw_code; - - return 0; -} -EXPORT_SYMBOL_GPL(qcom_smem_get_feature_code); - static int qcom_smem_get_sbl_version(struct qcom_smem *smem) { struct smem_header *header; @@ -874,8 +779,7 @@ static struct smem_ptable *qcom_smem_get_ptable(struct qcom_smem *smem) version = le32_to_cpu(ptable->version); if (version != 1) { - dev_err(smem->dev, - "Unsupported partition header version %d\n", version); + log_err("Unsupported partition header version %d\n", version); return ERR_PTR(-EINVAL); } return ptable; @@ -907,40 +811,40 @@ qcom_smem_partition_header(struct qcom_smem *smem, struct smem_ptable_entry *entry, u16 host0, u16 host1) { struct smem_partition_header *header; - u32 phys_addr; + u64 phys_addr; u32 size; phys_addr = smem->regions[0].aux_base + le32_to_cpu(entry->offset); - header = devm_ioremap_wc(smem->dev, phys_addr, le32_to_cpu(entry->size)); + header = (void *)phys_addr; // devm_ioremap_wc() if (!header) return NULL; if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) { - dev_err(smem->dev, "bad partition magic %4ph\n", header->magic); + log_err("bad partition magic %4ph\n", header->magic); return NULL; } if (host0 != le16_to_cpu(header->host0)) { - dev_err(smem->dev, "bad host0 (%hu != %hu)\n", - host0, le16_to_cpu(header->host0)); + log_err("bad host0 (%hu != %hu)\n", + host0, le16_to_cpu(header->host0)); return NULL; } if (host1 != le16_to_cpu(header->host1)) { - dev_err(smem->dev, "bad host1 (%hu != %hu)\n", - host1, le16_to_cpu(header->host1)); + log_err("bad host1 (%hu != %hu)\n", + host1, le16_to_cpu(header->host1)); return NULL; } size = le32_to_cpu(header->size); if (size != le32_to_cpu(entry->size)) { - dev_err(smem->dev, "bad partition size (%u != %u)\n", + log_err("bad partition size (%u != %u)\n", size, le32_to_cpu(entry->size)); return NULL; } if (le32_to_cpu(header->offset_free_uncached) > size) { - dev_err(smem->dev, "bad partition free uncached (%u > %u)\n", + log_err("bad partition free uncached (%u > %u)\n", le32_to_cpu(header->offset_free_uncached), size); return NULL; } @@ -957,7 +861,7 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem) int i; if (smem->global_partition.virt_base) { - dev_err(smem->dev, "Already found the global partition\n"); + log_err("Already found the global partition\n"); return -EINVAL; } @@ -982,7 +886,7 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem) } if (!found) { - dev_err(smem->dev, "Missing entry for global partition\n"); + log_err("Missing entry for global partition\n"); return -EINVAL; } @@ -1031,12 +935,12 @@ qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host) continue; if (remote_host >= SMEM_HOST_COUNT) { - dev_err(smem->dev, "bad host %u\n", remote_host); + log_err("bad host %u\n", remote_host); return -EINVAL; } if (smem->partitions[remote_host].virt_base) { - dev_err(smem->dev, "duplicate host %u\n", remote_host); + log_err("duplicate host %u\n", remote_host); return -EINVAL; } @@ -1059,25 +963,28 @@ static int qcom_smem_map_toc(struct qcom_smem *smem, struct smem_region *region) u32 ptable_start; /* map starting 4K for smem header */ - region->virt_base = devm_ioremap_wc(smem->dev, region->aux_base, SZ_4K); + region->virt_base = (void *)region->aux_base; ptable_start = region->aux_base + region->size - SZ_4K; /* map last 4k for toc */ - smem->ptable = devm_ioremap_wc(smem->dev, ptable_start, SZ_4K); + smem->ptable = (struct smem_ptable *)(u64)ptable_start; if (!region->virt_base || !smem->ptable) return -ENOMEM; + if (dcache_status()) + mmu_map_region(region->aux_base, region->size, false); + return 0; } static int qcom_smem_map_global(struct qcom_smem *smem, u32 size) { - u32 phys_addr; + u64 phys_addr; phys_addr = smem->regions[0].aux_base; smem->regions[0].size = size; - smem->regions[0].virt_base = devm_ioremap_wc(smem->dev, phys_addr, size); + smem->regions[0].virt_base = (void *)phys_addr; if (!smem->regions[0].virt_base) return -ENOMEM; @@ -1085,125 +992,69 @@ static int qcom_smem_map_global(struct qcom_smem *smem, u32 size) return 0; } -static int qcom_smem_resolve_mem(struct qcom_smem *smem, const char *name, - struct smem_region *region) -{ - struct device *dev = smem->dev; - struct device_node *np; - struct resource r; - int ret; - - np = of_parse_phandle(dev->of_node, name, 0); - if (!np) { - dev_err(dev, "No %s specified\n", name); - return -EINVAL; - } - - ret = of_address_to_resource(np, 0, &r); - of_node_put(np); - if (ret) - return ret; - - region->aux_base = r.start; - region->size = resource_size(&r); - - return 0; -} - -static int qcom_smem_probe(struct platform_device *pdev) +int qcom_smem_init(void) { struct smem_header *header; - struct reserved_mem *rmem; - struct qcom_smem *smem; - unsigned long flags; int num_regions; - int hwlock_id; + fdt_size_t reg_size = 0; + u32 phandle; + ofnode node, mem_node; u32 version; u32 size; int ret; int i; - if (__smem) + if (dcache_status() && smem) + mmu_map_region(smem->regions[0].aux_base, smem->regions[0].size, false); + + if (smem) return 0; - num_regions = 1; - if (of_property_present(pdev->dev.of_node, "qcom,rpm-msg-ram")) - num_regions++; + smem = &__smem; - smem = devm_kzalloc(&pdev->dev, struct_size(smem, regions, num_regions), - GFP_KERNEL); - if (!smem) - return -ENOMEM; + num_regions = 1; - smem->dev = &pdev->dev; - smem->num_regions = num_regions; + node = ofnode_by_compatible(ofnode_root(), "qcom,smem"); + if (!ofnode_valid(node)) + return -ENODEV; - rmem = of_reserved_mem_lookup(pdev->dev.of_node); - if (rmem) { - smem->regions[0].aux_base = rmem->base; - smem->regions[0].size = rmem->size; + if (ofnode_has_property(node, "memory-region")) { + ofnode_read_u32(node, "memory-region", &phandle); + mem_node = ofnode_get_by_phandle(phandle); } else { - /* - * Fall back to the memory-region reference, if we're not a - * reserved-memory node. - */ - ret = qcom_smem_resolve_mem(smem, "memory-region", &smem->regions[0]); - if (ret) - return ret; + mem_node = node; } - if (num_regions > 1) { - ret = qcom_smem_resolve_mem(smem, "qcom,rpm-msg-ram", &smem->regions[1]); - if (ret) - return ret; - } + smem->num_regions = num_regions; + smem->regions[0].aux_base = ofnode_get_addr(mem_node); + reg_size = ofnode_get_size(mem_node); + if (smem->regions[0].aux_base == FDT_ADDR_T_NONE) { + log_err("Failed to get base address\n"); + return -EINVAL; + } + smem->regions[0].size = reg_size; ret = qcom_smem_map_toc(smem, &smem->regions[0]); - if (ret) + if (ret) { + log_err("Failed to map toc\n"); return ret; + } for (i = 1; i < num_regions; i++) { - smem->regions[i].virt_base = devm_ioremap_wc(&pdev->dev, - smem->regions[i].aux_base, - smem->regions[i].size); - if (!smem->regions[i].virt_base) { - dev_err(&pdev->dev, "failed to remap %pa\n", &smem->regions[i].aux_base); - return -ENOMEM; - } + smem->regions[i].virt_base = (void *)smem->regions[i].aux_base; } header = smem->regions[0].virt_base; if (le32_to_cpu(header->initialized) != 1 || le32_to_cpu(header->reserved)) { - dev_err(&pdev->dev, "SMEM is not initialized by SBL\n"); + log_err("SMEM is not initialized by SBL\n"); return -EINVAL; } - hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); - if (hwlock_id < 0) { - if (hwlock_id != -EPROBE_DEFER) - dev_err(&pdev->dev, "failed to retrieve hwlock\n"); - return hwlock_id; - } - - smem->hwlock = hwspin_lock_request_specific(hwlock_id); - if (!smem->hwlock) - return -ENXIO; - - ret = hwspin_lock_timeout_irqsave(smem->hwlock, HWSPINLOCK_TIMEOUT, &flags); - if (ret) - return ret; size = readl_relaxed(&header->available) + readl_relaxed(&header->free_offset); - hwspin_unlock_irqrestore(smem->hwlock, &flags); version = qcom_smem_get_sbl_version(smem); - /* - * smem header mapping is required only in heap version scheme, so unmap - * it here. It will be remapped in qcom_smem_map_global() when whole - * partition is mapped again. - */ - devm_iounmap(smem->dev, smem->regions[0].virt_base); switch (version >> 16) { case SMEM_GLOBAL_PART_VERSION: ret = qcom_smem_set_global_partition(smem); @@ -1216,62 +1067,16 @@ static int qcom_smem_probe(struct platform_device *pdev) smem->item_count = SMEM_ITEM_COUNT; break; default: - dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version); + log_err("Unsupported SMEM version 0x%x\n", version); return -EINVAL; } BUILD_BUG_ON(SMEM_HOST_APPS >= SMEM_HOST_COUNT); ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); - if (ret < 0 && ret != -ENOENT) + if (ret < 0 && ret != -ENOENT) { + log_err("Failed to enumerate partitions\n"); return ret; - - __smem = smem; - - smem->socinfo = platform_device_register_data(&pdev->dev, "qcom-socinfo", - PLATFORM_DEVID_NONE, NULL, - 0); - if (IS_ERR(smem->socinfo)) - dev_dbg(&pdev->dev, "failed to register socinfo device\n"); + } return 0; } - -static void qcom_smem_remove(struct platform_device *pdev) -{ - platform_device_unregister(__smem->socinfo); - - hwspin_lock_free(__smem->hwlock); - __smem = NULL; -} - -static const struct of_device_id qcom_smem_of_match[] = { - { .compatible = "qcom,smem" }, - {} -}; -MODULE_DEVICE_TABLE(of, qcom_smem_of_match); - -static struct platform_driver qcom_smem_driver = { - .probe = qcom_smem_probe, - .remove_new = qcom_smem_remove, - .driver = { - .name = "qcom-smem", - .of_match_table = qcom_smem_of_match, - .suppress_bind_attrs = true, - }, -}; - -static int __init qcom_smem_init(void) -{ - return platform_driver_register(&qcom_smem_driver); -} -arch_initcall(qcom_smem_init); - -static void __exit qcom_smem_exit(void) -{ - platform_driver_unregister(&qcom_smem_driver); -} -module_exit(qcom_smem_exit) - -MODULE_AUTHOR("Bjorn Andersson "); -MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); -MODULE_LICENSE("GPL v2"); diff --git a/include/soc/qcom/smem.h b/include/soc/qcom/smem.h index f946e3beca21..a955db08c0f0 100644 --- a/include/soc/qcom/smem.h +++ b/include/soc/qcom/smem.h @@ -4,17 +4,12 @@ #define QCOM_SMEM_HOST_ANY -1 +int qcom_smem_init(void); + bool qcom_smem_is_available(void); int qcom_smem_alloc(unsigned host, unsigned item, size_t size); void *qcom_smem_get(unsigned host, unsigned item, size_t *size); int qcom_smem_get_free_space(unsigned host); -phys_addr_t qcom_smem_virt_to_phys(void *p); - -int qcom_smem_get_soc_id(u32 *id); -int qcom_smem_get_feature_code(u32 *code); - -int qcom_smem_bust_hwspin_lock_by_host(unsigned int host); - #endif From 9d41fc19fd0a49fee8b7b38b8ae595125fe8e191 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:37 +0200 Subject: [PATCH 51/70] soc: qcom: smem: get serial number from socinfo Implement socinfo support to fetch the serial number if available. Signed-off-by: Casey Connolly --- drivers/soc/qcom/smem.c | 25 +++++++++++++++++++++++++ include/soc/qcom/smem.h | 1 + 2 files changed, 26 insertions(+) diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 96288d7bcdef..e20523e2cec3 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include /* * The Qualcomm shared memory system is a allocate only heap structure that @@ -992,6 +994,29 @@ static int qcom_smem_map_global(struct qcom_smem *smem, u32 size) return 0; } +int qcom_socinfo_init(void) +{ + struct socinfo *info; + size_t item_size; + char buf[32] = { 0 }; + + info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID, + &item_size); + if (IS_ERR(info)) { + log_err("Couldn't find socinfo: %ld\n", PTR_ERR(info)); + return PTR_ERR(info); + } + + if (offsetof(struct socinfo, serial_num) + sizeof(info->serial_num) <= item_size) { + snprintf(buf, sizeof(buf), "%u", le32_to_cpu(info->serial_num)); + env_set("serial#", buf); + } else { + return -ENOENT; + } + + return 0; +} + int qcom_smem_init(void) { struct smem_header *header; diff --git a/include/soc/qcom/smem.h b/include/soc/qcom/smem.h index a955db08c0f0..755003807dba 100644 --- a/include/soc/qcom/smem.h +++ b/include/soc/qcom/smem.h @@ -5,6 +5,7 @@ #define QCOM_SMEM_HOST_ANY -1 int qcom_smem_init(void); +int qcom_socinfo_init(void); bool qcom_smem_is_available(void); int qcom_smem_alloc(unsigned host, unsigned item, size_t size); From df47f1a9619ce9cb5a54d620076e6eee574883ef Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:38 +0200 Subject: [PATCH 52/70] soc: qcom: smem: stub functions Allow smem to be optional for Qualcomm platforms by providing stub functions. Signed-off-by: Casey Connolly Reviewed-by: Simon Glass --- include/soc/qcom/smem.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/soc/qcom/smem.h b/include/soc/qcom/smem.h index 755003807dba..586432412eb8 100644 --- a/include/soc/qcom/smem.h +++ b/include/soc/qcom/smem.h @@ -2,8 +2,12 @@ #ifndef __QCOM_SMEM_H__ #define __QCOM_SMEM_H__ +#include +#include + #define QCOM_SMEM_HOST_ANY -1 +#if defined(CONFIG_QCOM_SMEM) int qcom_smem_init(void); int qcom_socinfo_init(void); @@ -12,5 +16,21 @@ int qcom_smem_alloc(unsigned host, unsigned item, size_t size); void *qcom_smem_get(unsigned host, unsigned item, size_t *size); int qcom_smem_get_free_space(unsigned host); +#else +static int qcom_smem_init(void) { return -ENOSYS; } + +static bool qcom_smem_is_available(void) { return false; } +int qcom_smem_alloc(unsigned host, unsigned item, size_t size) +{ + return -ENOSYS; +} + +void *qcom_smem_get(unsigned host, unsigned item, size_t *size) +{ + return ERR_PTR(-ENOSYS); +} + +int qcom_smem_get_free_space(unsigned host); +#endif #endif From 6e66806baac9307e72f609c14b0dc291d8cd7d33 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:39 +0200 Subject: [PATCH 53/70] soc: qcom: smem: add build infra Build the new SMEM driver port, and select it when ARCH_SNAPDRAGON is selected, since it will be a hard dependency for Qualcomm platforms. Signed-off-by: Casey Connolly Reviewed-by: Simon Glass --- drivers/soc/qcom/Kconfig | 8 ++++++++ drivers/soc/qcom/Makefile | 1 + 2 files changed, 9 insertions(+) diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 8243805e46a3..cdd9e30f43e5 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -25,4 +25,12 @@ config QCOM_RPMH Say y here to support the Qualcomm RPMh (resource peripheral manager) if you need to control regulators on Qualcomm platforms, say y here. +config QCOM_SMEM + bool "Qualcomm SMEM support" + help + Say y here to support the Qualcomm SMEM (shared memory) client driver. + SMEM is a shared memory region that is used to pass information about + the hardware (e.g. DRAM layout, PMIC configuration, etc) between bootloader + staged and the OS. If in doubt, say y. + endif # SOC_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 78fae8bbfa16..f4102f9155a8 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o obj-$(CONFIG_QCOM_RPMH) += rpmh-rsc.o rpmh.o +obj-$(CONFIG_QCOM_SMEM) += smem.o From ad7399c527756c05c223e3dcade1acde6e4af3af Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:40 +0200 Subject: [PATCH 54/70] mach-snapdragon: move memory parsing to its own file This code is getting a bit complicated, split it out to try and keep things a bit better organised as we're going to be supporting populating the memory layout from various other sources. Signed-off-by: Casey Connolly --- arch/arm/mach-snapdragon/Makefile | 2 +- arch/arm/mach-snapdragon/board.c | 115 ------------------------ arch/arm/mach-snapdragon/dram.c | 127 +++++++++++++++++++++++++++ arch/arm/mach-snapdragon/qcom-priv.h | 2 + 4 files changed, 130 insertions(+), 116 deletions(-) create mode 100644 arch/arm/mach-snapdragon/dram.c diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-snapdragon/Makefile index 343e825c6fdd..e481e4f26e5c 100644 --- a/arch/arm/mach-snapdragon/Makefile +++ b/arch/arm/mach-snapdragon/Makefile @@ -2,6 +2,6 @@ # # (C) Copyright 2015 Mateusz Kulikowski -obj-y += board.o +obj-y += board.o dram.o obj-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += capsule_update.o obj-$(CONFIG_OF_LIVE) += of_fixup.o diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-snapdragon/board.c index 0602782a1081..b33ea2cee2bc 100644 --- a/arch/arm/mach-snapdragon/board.c +++ b/arch/arm/mach-snapdragon/board.c @@ -45,121 +45,6 @@ static struct mm_region rbx_mem_map[CONFIG_NR_DRAM_BANKS + 2] = { { 0 } }; struct mm_region *mem_map = rbx_mem_map; -static struct { - phys_addr_t start; - phys_size_t size; -} prevbl_ddr_banks[CONFIG_NR_DRAM_BANKS] __section(".data") = { 0 }; - -int dram_init(void) -{ - /* - * gd->ram_base / ram_size have been setup already - * in qcom_parse_memory(). - */ - return 0; -} - -static int ddr_bank_cmp(const void *v1, const void *v2) -{ - const struct { - phys_addr_t start; - phys_size_t size; - } *res1 = v1, *res2 = v2; - - if (!res1->size) - return 1; - if (!res2->size) - return -1; - - return (res1->start >> 24) - (res2->start >> 24); -} - -/* This has to be done post-relocation since gd->bd isn't preserved */ -static void qcom_configure_bi_dram(void) -{ - int i; - - for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { - gd->bd->bi_dram[i].start = prevbl_ddr_banks[i].start; - gd->bd->bi_dram[i].size = prevbl_ddr_banks[i].size; - } -} - -int dram_init_banksize(void) -{ - qcom_configure_bi_dram(); - - return 0; -} - -/** - * The generic memory parsing code in U-Boot lacks a few things that we - * need on Qualcomm: - * - * 1. It sets gd->ram_size and gd->ram_base to represent a single memory block - * 2. setup_dest_addr() later relocates U-Boot to ram_base + ram_size, the end - * of that first memory block. - * - * This results in all memory beyond U-Boot being unusable in Linux when booting - * with EFI. - * - * Since the ranges in the memory node may be out of order, the only way for us - * to correctly determine the relocation address for U-Boot is to parse all - * memory regions and find the highest valid address. - * - * We can't use fdtdec_setup_memory_banksize() since it stores the result in - * gd->bd, which is not yet allocated. - * - * @fdt: FDT blob to parse /memory node from - * - * Return: 0 on success or -ENODATA if /memory node is missing or incomplete - */ -static int qcom_parse_memory(const void *fdt) -{ - int offset; - const fdt64_t *memory; - int memsize; - phys_addr_t ram_end = 0; - int i, j, banks; - - offset = fdt_path_offset(fdt, "/memory"); - if (offset < 0) - return -ENODATA; - - memory = fdt_getprop(fdt, offset, "reg", &memsize); - if (!memory) - return -ENODATA; - - banks = min(memsize / (2 * sizeof(u64)), (ulong)CONFIG_NR_DRAM_BANKS); - - if (memsize / sizeof(u64) > CONFIG_NR_DRAM_BANKS * 2) - log_err("Provided more than the max of %d memory banks\n", CONFIG_NR_DRAM_BANKS); - - if (banks > CONFIG_NR_DRAM_BANKS) - log_err("Provided more memory banks than we can handle\n"); - - for (i = 0, j = 0; i < banks * 2; i += 2, j++) { - prevbl_ddr_banks[j].start = get_unaligned_be64(&memory[i]); - prevbl_ddr_banks[j].size = get_unaligned_be64(&memory[i + 1]); - if (!prevbl_ddr_banks[j].size) { - j--; - continue; - } - ram_end = max(ram_end, prevbl_ddr_banks[j].start + prevbl_ddr_banks[j].size); - } - - if (!banks || !prevbl_ddr_banks[0].size) - return -ENODATA; - - /* Sort our RAM banks -_- */ - qsort(prevbl_ddr_banks, banks, sizeof(prevbl_ddr_banks[0]), ddr_bank_cmp); - - gd->ram_base = prevbl_ddr_banks[0].start; - gd->ram_size = ram_end - gd->ram_base; - - return 0; -} - static void show_psci_version(void) { struct arm_smccc_res res; diff --git a/arch/arm/mach-snapdragon/dram.c b/arch/arm/mach-snapdragon/dram.c new file mode 100644 index 000000000000..ad73b685a935 --- /dev/null +++ b/arch/arm/mach-snapdragon/dram.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Memory layout parsing for Qualcomm. + */ + +#define LOG_CATEGORY LOGC_BOARD +#define pr_fmt(fmt) "QCOM-DRAM: " fmt + +#include +#include +#include +#include + +static struct { + phys_addr_t start; + phys_size_t size; +} prevbl_ddr_banks[CONFIG_NR_DRAM_BANKS] __section(".data") = { 0 }; + +int dram_init(void) +{ + /* + * gd->ram_base / ram_size have been setup already + * in qcom_parse_memory(). + */ + return 0; +} + +static int ddr_bank_cmp(const void *v1, const void *v2) +{ + const struct { + phys_addr_t start; + phys_size_t size; + } *res1 = v1, *res2 = v2; + + if (!res1->size) + return 1; + if (!res2->size) + return -1; + + return (res1->start >> 24) - (res2->start >> 24); +} + +/* This has to be done post-relocation since gd->bd isn't preserved */ +static void qcom_configure_bi_dram(void) +{ + int i; + + for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { + gd->bd->bi_dram[i].start = prevbl_ddr_banks[i].start; + gd->bd->bi_dram[i].size = prevbl_ddr_banks[i].size; + } +} + +int dram_init_banksize(void) +{ + qcom_configure_bi_dram(); + + return 0; +} + +/** + * The generic memory parsing code in U-Boot lacks a few things that we + * need on Qualcomm: + * + * 1. It sets gd->ram_size and gd->ram_base to represent a single memory block + * 2. setup_dest_addr() later relocates U-Boot to ram_base + ram_size, the end + * of that first memory block. + * + * This results in all memory beyond U-Boot being unusable in Linux when booting + * with EFI. + * + * Since the ranges in the memory node may be out of order, the only way for us + * to correctly determine the relocation address for U-Boot is to parse all + * memory regions and find the highest valid address. + * + * We can't use fdtdec_setup_memory_banksize() since it stores the result in + * gd->bd, which is not yet allocated. + * + * @fdt: FDT blob to parse /memory node from + * + * Return: 0 on success or -ENODATA if /memory node is missing or incomplete + */ +int qcom_parse_memory(const void *fdt) +{ + int offset; + const fdt64_t *memory; + int memsize; + phys_addr_t ram_end = 0; + int i, j, banks; + + offset = fdt_path_offset(fdt, "/memory"); + if (offset < 0) + return -ENODATA; + + memory = fdt_getprop(fdt, offset, "reg", &memsize); + if (!memory) + return -ENODATA; + + banks = min(memsize / (2 * sizeof(u64)), (ulong)CONFIG_NR_DRAM_BANKS); + + if (memsize / sizeof(u64) > CONFIG_NR_DRAM_BANKS * 2) + log_err("Provided more than the max of %d memory banks\n", CONFIG_NR_DRAM_BANKS); + + if (banks > CONFIG_NR_DRAM_BANKS) + log_err("Provided more memory banks than we can handle\n"); + + for (i = 0, j = 0; i < banks * 2; i += 2, j++) { + prevbl_ddr_banks[j].start = get_unaligned_be64(&memory[i]); + prevbl_ddr_banks[j].size = get_unaligned_be64(&memory[i + 1]); + if (!prevbl_ddr_banks[j].size) { + j--; + continue; + } + ram_end = max(ram_end, prevbl_ddr_banks[j].start + prevbl_ddr_banks[j].size); + } + + if (!banks || !prevbl_ddr_banks[0].size) + return -ENODATA; + + /* Sort our RAM banks -_- */ + qsort(prevbl_ddr_banks, banks, sizeof(prevbl_ddr_banks[0]), ddr_bank_cmp); + + gd->ram_base = prevbl_ddr_banks[0].start; + gd->ram_size = ram_end - gd->ram_base; + + return 0; +} diff --git a/arch/arm/mach-snapdragon/qcom-priv.h b/arch/arm/mach-snapdragon/qcom-priv.h index b8bf574e8bbb..ce409314a98b 100644 --- a/arch/arm/mach-snapdragon/qcom-priv.h +++ b/arch/arm/mach-snapdragon/qcom-priv.h @@ -23,4 +23,6 @@ void qcom_configure_capsule_updates(void); void qcom_configure_capsule_updates(void) {} #endif /* EFI_HAVE_CAPSULE_SUPPORT */ +int qcom_parse_memory(const void *fdt); + #endif /* __QCOM_PRIV_H__ */ From 14afd1f5f2c3e343087db30a177b36807e68242f Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:41 +0200 Subject: [PATCH 55/70] mach-snapdragon: support parsing memory map from SMEM It is possible to derive the memory map for a Qualcomm platform from the SMEM shared memory region. The memory map is populated by the preloader. Introduce support for parsing this data and using it to populate U-Boot's memory map. Since we aren't yet sure if this will work for every platform, it is not yet used in all cases, if U-Boot is booted with an internal FDT which has the memory map defined, this will be used instead. If the FDT is internal FDT with no memory map defined, then U-Boot will try to use SMEM. This should remove the need to define the memory map statically in a U-Boot overlay for boards that don't chainload. Signed-off-by: Casey Connolly --- arch/arm/Kconfig | 1 + arch/arm/mach-snapdragon/board.c | 45 +++++-- arch/arm/mach-snapdragon/dram.c | 172 +++++++++++++++++++----- arch/arm/mach-snapdragon/qcom-priv.h | 17 ++- arch/arm/mach-snapdragon/rampart.h | 194 +++++++++++++++++++++++++++ 5 files changed, 386 insertions(+), 43 deletions(-) create mode 100644 arch/arm/mach-snapdragon/rampart.h diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index a8fcfe76296c..1da0677d85b3 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1141,6 +1141,7 @@ config ARCH_SNAPDRAGON select POWER_DOMAIN select GPIO_EXTRA_HEADER select OF_CONTROL + select QCOM_SMEM select SPMI select BOARD_LATE_INIT select OF_BOARD diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-snapdragon/board.c index b33ea2cee2bc..a8a85cd9aec5 100644 --- a/arch/arm/mach-snapdragon/board.c +++ b/arch/arm/mach-snapdragon/board.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,7 @@ DECLARE_GLOBAL_DATA_PTR; enum qcom_boot_source qcom_boot_source __section(".data") = 0; +enum qcom_memmap_source qcom_memmap_source __section(".data") = 0; static struct mm_region rbx_mem_map[CONFIG_NR_DRAM_BANKS + 2] = { { 0 } }; @@ -112,7 +114,7 @@ int board_fdt_blob_setup(void **fdtp) /* Prefer memory information from internal DT if it's present */ if (internal_valid) - ret = qcom_parse_memory(internal_fdt); + ret = qcom_parse_memory(internal_fdt, true); if (ret < 0 && external_valid) { /* No internal FDT or it lacks a proper /memory node. @@ -121,7 +123,7 @@ int board_fdt_blob_setup(void **fdtp) if (internal_valid) debug("No memory info in internal FDT, falling back to external\n"); - ret = qcom_parse_memory(external_fdt); + ret = qcom_parse_memory(external_fdt, false); } if (ret < 0) @@ -376,21 +378,37 @@ static void configure_env(void) qcom_set_serialno(); } -void qcom_show_boot_source(void) +static void qcom_show_boot_context(void) { - const char *name = "UNKNOWN"; + const char *boot_source = "UNKNOWN"; + const char *memmap_source = "UNKNOWN"; switch (qcom_boot_source) { case QCOM_BOOT_SOURCE_ANDROID: - name = "ABL"; + boot_source = "ABL"; break; case QCOM_BOOT_SOURCE_XBL: - name = "XBL"; + boot_source = "XBL"; break; } - log_info("U-Boot loaded from %s\n", name); - env_set("boot_source", name); + log_info("U-Boot loaded from %s\n", boot_source); + env_set("boot_source", boot_source); + + switch (qcom_memmap_source) { + case QCOM_MEMMAP_SOURCE_INTERNAL_FDT: + memmap_source = "INTERNAL_FDT"; + break; + case QCOM_MEMMAP_SOURCE_EXTERNAL_FDT: + memmap_source = "EXTERNAL_FDT"; + break; + case QCOM_MEMMAP_SOURCE_SMEM: + memmap_source = "SMEM"; + break; + } + + log_info("Memory map loaded from %s\n", memmap_source); + env_set("memmap_source", memmap_source); } void __weak qcom_late_init(void) @@ -461,7 +479,7 @@ int board_late_init(void) configure_env(); qcom_late_init(); - qcom_show_boot_source(); + qcom_show_boot_context(); /* Configure the dfu_string for capsule updates */ qcom_configure_capsule_updates(); @@ -707,9 +725,12 @@ void enable_caches(void) gd->arch.tlb_addr = tlb_addr; gd->arch.tlb_size = tlb_size; - /* On some boards speculative access may trigger a NOC or XPU violation so explicitly mark reserved - * regions as inacessible (PTE_TYPE_FAULT) */ - if (fdt_node_check_compatible(gd->fdt_blob, 0, "qcom,qcs404") == 0) { + /* + * On some boards speculative access may trigger a NOC or XPU violation so explicitly mark + * reserved regions as inacessible (PTE_TYPE_FAULT) + */ + if (qcom_memmap_source == QCOM_MEMMAP_SOURCE_SMEM || + fdt_node_check_compatible(gd->fdt_blob, 0, "qcom,qcs404") == 0) { carveout_start = get_timer(0); /* Takes ~20-50ms on SDM845 */ configure_reserved_memory(); diff --git a/arch/arm/mach-snapdragon/dram.c b/arch/arm/mach-snapdragon/dram.c index ad73b685a935..af5f3c84882c 100644 --- a/arch/arm/mach-snapdragon/dram.c +++ b/arch/arm/mach-snapdragon/dram.c @@ -10,6 +10,10 @@ #include #include #include +#include + +#include "qcom-priv.h" +#include "rampart.h" static struct { phys_addr_t start; @@ -48,6 +52,10 @@ static void qcom_configure_bi_dram(void) for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { gd->bd->bi_dram[i].start = prevbl_ddr_banks[i].start; gd->bd->bi_dram[i].size = prevbl_ddr_banks[i].size; + debug("Bank[%d]: start = %#011llx, size = %#011llx\n", + i, gd->bd->bi_dram[i].start, gd->bd->bi_dram[i].size); + if (!prevbl_ddr_banks[i].size) + break; } } @@ -58,6 +66,122 @@ int dram_init_banksize(void) return 0; } +#define entry_field(v, e, field) (v == 0 ? ((struct ram_partition_entry_v0 *)(e))->field : \ + (v == 1 ? ((struct ram_partition_entry_v1 *)(e))->field : \ + ((struct ram_partition_entry_v3 *)(e))->field)) +#define entry_start(v, e) entry_field(v, e, start_address) +#define entry_length(v, e) entry_field(v, e, length) +#define entry_category(v, e) entry_field(v, e, partition_category) +#define entry_domain(v, e) entry_field(v, e, partition_domain) +#define entry_type(v, e) entry_field(v, e, partition_type) + +/* Parse memory map from SMEM, return the number of entries */ +static int qcom_parse_memory_smem(phys_addr_t *ram_end) +{ + size_t size; + int i, j = 0, ret; + struct usable_ram_partition_table_header *header; + u32 ver; + void *entry; + u32 entry_size; // Size of each RAM partition entry (version dependent) + + ret = qcom_smem_init(); + if (ret) { + debug("Failed to initialize SMEM: %d.\n", ret); + return ret; + } + + header = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_USABLE_RAM_PARTITION_TABLE, &size); + if (!header) { + debug("Failed to find SMEM partition.\n"); + return -ENODEV; + } + + ver = header->version; + debug("SMEM RAM partition table version %u. %u entries\n", ver, header->num_partitions); + + switch (ver) { + case 0: + entry_size = sizeof(struct ram_partition_entry_v0); + entry = ((struct usable_ram_partition_table_v0 *)header)->entries; + break; + case 1: + entry_size = sizeof(struct ram_partition_entry_v1); + entry = ((struct usable_ram_partition_table_v1 *)header)->entries; + break; + default: + pr_warn("Unknown SMEM ram partition table version!\n"); + case 2: + case 3: + entry_size = sizeof(struct ram_partition_entry_v3); + entry = ((struct usable_ram_partition_table_v3 *)header)->entries; + break; + } + + debug("SMEM RAM partition entry size: %u bytes\n", entry_size); + + /* Check validy of RAM */ + for (i = 0; i < header->num_partitions && j < CONFIG_NR_DRAM_BANKS; i++, entry += entry_size) { + debug("Entry %d: [%#010llx - %#010llx]\n", i, entry_start(ver, entry), + (u64)entry_start(ver, entry) + entry_length(ver, entry)); + debug(" cat: %#04x type: %#04x domain: %#04x\n", entry_category(ver, entry), + entry_type(ver, entry), entry_domain(ver, entry)); + + + if (entry_category(ver, entry) != RAM_PARTITION_SDRAM || + entry_type(ver, entry) != RAM_PARTITION_SYS_MEMORY) + continue; + if (!entry_length(ver, entry) && !entry_start(ver, entry)) + break; + + prevbl_ddr_banks[j].start = entry_start(ver, entry); + prevbl_ddr_banks[j].size = entry_length(ver, entry); + *ram_end = max(*ram_end, prevbl_ddr_banks[j].start + prevbl_ddr_banks[j].size); + j++; + } + + if (j == CONFIG_NR_DRAM_BANKS) + pr_err("SMEM: More than CONFIG_NR_DRAM_BANKS (%u) entries!", CONFIG_NR_DRAM_BANKS); + + return j; +} + +static void qcom_parse_memory_dt(const fdt64_t *fdt, int *banks, phys_addr_t *ram_end) +{ + int offset; + const fdt64_t *memory; + int memsize; + int i, j; + + *ram_end = 0; + + offset = fdt_path_offset(fdt, "/memory"); + if (offset < 0) + return; + + memory = fdt_getprop(fdt, offset, "reg", &memsize); + if (!memory) + return; + + *banks = min(memsize / (2 * sizeof(u64)), (ulong)CONFIG_NR_DRAM_BANKS); + + if (memsize / sizeof(u64) > CONFIG_NR_DRAM_BANKS * 2) + log_err("Provided more than the max of %d memory banks\n", CONFIG_NR_DRAM_BANKS); + + if (*banks > CONFIG_NR_DRAM_BANKS) + log_err("Provided more memory banks than we can handle\n"); + + for (i = 0, j = 0; i < *banks * 2; i += 2, j++) { + prevbl_ddr_banks[j].start = get_unaligned_be64(&memory[i]); + prevbl_ddr_banks[j].size = get_unaligned_be64(&memory[i + 1]); + if (!prevbl_ddr_banks[j].size) { + j--; + continue; + } + *ram_end = max(*ram_end, prevbl_ddr_banks[j].start + prevbl_ddr_banks[j].size); + } +} + /** * The generic memory parsing code in U-Boot lacks a few things that we * need on Qualcomm: @@ -77,46 +201,34 @@ int dram_init_banksize(void) * gd->bd, which is not yet allocated. * * @fdt: FDT blob to parse /memory node from + * @fdt_is_internal: is the FDT one embedded into U-Boot or was it provided by a prior + * bootloader stage? This determined if we should try to rely on SMEM + * (internal FDT) or error. We currently assume that if we are passed + * an external FDT then it already has the memory map populated. * * Return: 0 on success or -ENODATA if /memory node is missing or incomplete */ -int qcom_parse_memory(const void *fdt) +int qcom_parse_memory(const void *fdt, bool fdt_is_internal) { - int offset; - const fdt64_t *memory; - int memsize; phys_addr_t ram_end = 0; - int i, j, banks; + int banks; - offset = fdt_path_offset(fdt, "/memory"); - if (offset < 0) - return -ENODATA; + qcom_memmap_source = fdt_is_internal ? QCOM_MEMMAP_SOURCE_INTERNAL_FDT + : QCOM_MEMMAP_SOURCE_EXTERNAL_FDT; - memory = fdt_getprop(fdt, offset, "reg", &memsize); - if (!memory) - return -ENODATA; - - banks = min(memsize / (2 * sizeof(u64)), (ulong)CONFIG_NR_DRAM_BANKS); - - if (memsize / sizeof(u64) > CONFIG_NR_DRAM_BANKS * 2) - log_err("Provided more than the max of %d memory banks\n", CONFIG_NR_DRAM_BANKS); - - if (banks > CONFIG_NR_DRAM_BANKS) - log_err("Provided more memory banks than we can handle\n"); + qcom_parse_memory_dt(fdt, &banks, &ram_end); - for (i = 0, j = 0; i < banks * 2; i += 2, j++) { - prevbl_ddr_banks[j].start = get_unaligned_be64(&memory[i]); - prevbl_ddr_banks[j].size = get_unaligned_be64(&memory[i + 1]); - if (!prevbl_ddr_banks[j].size) { - j--; - continue; - } - ram_end = max(ram_end, prevbl_ddr_banks[j].start + prevbl_ddr_banks[j].size); + /* + * If using an internal FDT but the memory node is empty + * then fall back to SMEM. + */ + if (!prevbl_ddr_banks[0].size && fdt_is_internal) { + banks = qcom_parse_memory_smem(&ram_end); + if (banks < 0) + panic("Couldn't find a valid memory map!\n"); + qcom_memmap_source = QCOM_MEMMAP_SOURCE_SMEM; } - if (!banks || !prevbl_ddr_banks[0].size) - return -ENODATA; - /* Sort our RAM banks -_- */ qsort(prevbl_ddr_banks, banks, sizeof(prevbl_ddr_banks[0]), ddr_bank_cmp); diff --git a/arch/arm/mach-snapdragon/qcom-priv.h b/arch/arm/mach-snapdragon/qcom-priv.h index ce409314a98b..39dc8fcc76ad 100644 --- a/arch/arm/mach-snapdragon/qcom-priv.h +++ b/arch/arm/mach-snapdragon/qcom-priv.h @@ -3,6 +3,8 @@ #ifndef __QCOM_PRIV_H__ #define __QCOM_PRIV_H__ +#include + /** * enum qcom_boot_source - Track where we got loaded from. * Used for capsule update logic. @@ -17,12 +19,25 @@ enum qcom_boot_source { extern enum qcom_boot_source qcom_boot_source; +/* + * enum qcom_memmap_source - Track where we got the memory map from. + * used for debugging and validation. + */ +enum qcom_memmap_source { + QCOM_MEMMAP_SOURCE_INTERNAL_FDT = 1, + QCOM_MEMMAP_SOURCE_EXTERNAL_FDT, + QCOM_MEMMAP_SOURCE_SMEM, +}; + +/* Set by qcom_parse_memory() */ +extern enum qcom_memmap_source qcom_memmap_source; + #if IS_ENABLED(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) void qcom_configure_capsule_updates(void); #else void qcom_configure_capsule_updates(void) {} #endif /* EFI_HAVE_CAPSULE_SUPPORT */ -int qcom_parse_memory(const void *fdt); +int qcom_parse_memory(const void *fdt, bool fdt_is_internal); #endif /* __QCOM_PRIV_H__ */ diff --git a/arch/arm/mach-snapdragon/rampart.h b/arch/arm/mach-snapdragon/rampart.h new file mode 100644 index 000000000000..57f3dbb7eec0 --- /dev/null +++ b/arch/arm/mach-snapdragon/rampart.h @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RAM partition table definitions + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + */ + +#ifndef __QCOM_SMEM_RAMPART_H__ +#define __QCOM_SMEM_RAMPART_H__ + +#include + +#define SMEM_USABLE_RAM_PARTITION_TABLE 402 + +#define RAM_PARTITION_H_MAJOR 03 +#define RAM_PARTITION_H_MINOR 00 + +/** + * Total length of zero filled name string. This is not a C + * string, as it can occupy the total number of bytes, and if + * it does, it does not require a zero terminator. It cannot + * be manipulated with standard string handling library functions. + */ +#define RAM_PART_NAME_LENGTH 16 + +/** + * Number of RAM partition entries which are usable by APPS. + */ +#define RAM_NUM_PART_ENTRIES 32 + +/** + * @name: Magic numbers + * Used in identifying valid RAM partition table. + */ +#define RAM_PART_MAGIC1 0x9da5e0a8 +#define RAM_PART_MAGIC2 0xaf9ec4e2 + +/** + * RAM partition attributes. + */ +enum ram_partition_attribute_t { + RAM_PARTITION_READ_ONLY = 0, /* Read-only RAM partition */ + RAM_PARTITION_READWRITE, /* Read/write RAM partition */ +}; + +/** + * RAM partition categories. + */ +enum ram_partition_category_t { + RAM_PARTITION_IRAM = 4, /* IRAM RAM partition */ + RAM_PARTITION_IMEM = 5, /* IMEM RAM partition */ + RAM_PARTITION_SDRAM = 14, /* SDRAM type without specific bus information**/ +}; + +/** + * RAM Partition domains. + * @note: For shared RAM partition, domain value would be 0b11:\n + * RAM_PARTITION_APPS_DOMAIN | RAM_PARTITION_MODEM_DOMAIN. + */ +enum ram_partition_domain_t { + RAM_PARTITION_DEFAULT_DOMAIN = 0, /* 0b00: No specific domain definition */ + RAM_PARTITION_APPS_DOMAIN = 1, /* 0b01: APPS RAM partition */ + RAM_PARTITION_MODEM_DOMAIN = 2, /* 0b10: MODEM RAM partition */ +}; + +/** + * RAM Partition types. + * @note: The RAM_PARTITION_SYS_MEMORY type represents DDR rams that are attached + * to the current system. + */ +enum ram_partition_type_t { + RAM_PARTITION_SYS_MEMORY = 1, /* system memory */ + RAM_PARTITION_BOOT_REGION_MEMORY1, /* boot loader memory 1 */ + RAM_PARTITION_BOOT_REGION_MEMORY2, /* boot loader memory 2, reserved */ + RAM_PARTITION_APPSBL_MEMORY, /* apps boot loader memory */ + RAM_PARTITION_APPS_MEMORY, /* apps usage memory */ + RAM_PARTITION_TOOLS_FV_MEMORY, /* tools usage memory */ + RAM_PARTITION_QUANTUM_FV_MEMORY, /* quantum usage memory */ + RAM_PARTITION_QUEST_FV_MEMORY, /* quest usage memory */ +}; + +/* Common table header between versions */ +struct usable_ram_partition_table_header { + u32 magic1; /* Magic number to identify valid RAM partition table */ + u32 magic2; /* Magic number to identify valid RAM partition table */ + u32 version; /* Version number to track structure definition changes */ + u32 reserved1; /* Reserved for future use */ + + u32 num_partitions; /* Number of RAM partition table entries */ +}; + +/* Holds information for an entry in the RAM partition table */ +struct ram_partition_entry_v3 { + char name[RAM_PART_NAME_LENGTH]; /* Partition name, unused for now */ + u64 start_address; /* Partition start address in RAM */ + u64 length; /* Partition length in RAM in Bytes */ + u32 partition_attribute; /* Partition attribute */ + u32 partition_category; /* Partition category */ + u32 partition_domain; /* Partition domain */ + u32 partition_type; /* Partition type */ + u32 num_partitions; /* Number of partitions on device */ + u32 hw_info; /* hw information such as type and frequency */ + u8 highest_bank_bit; /* Highest bit corresponding to a bank */ + u8 reserve0; /* Reserved for future use */ + u8 reserve1; /* Reserved for future use */ + u8 reserve2; /* Reserved for future use */ + u32 min_pasr_size; /* Minimum PASR size in MB */ + u64 available_length; /* Available Partition length in RAM in Bytes */ +}; + +/* + * Defines the RAM partition table structure + * + * Do not change the placement of the first four elements so that future + * compatibility will always be guaranteed at least for the identifiers. + * + * The other portion of the structure may be changed as necessary to accommodate + * new features. Be sure to increment version number if you change it. + */ +struct usable_ram_partition_table_v3 { + struct usable_ram_partition_table_header header; + + u32 reserved2; /* Added for 8 bytes alignment of header */ + + /* RAM partition table entries */ + struct ram_partition_entry_v3 entries[RAM_NUM_PART_ENTRIES]; +}; + +/* Version 1 structure 32 Bit - Holds information for an entry in the RAM partition table */ +struct ram_partition_entry_v1 { + char name[RAM_PART_NAME_LENGTH]; /* Partition name, unused for now */ + u64 start_address; /* Partition start address in RAM */ + u64 length; /* Partition length in RAM in Bytes */ + u32 partition_attribute; /* Partition attribute */ + u32 partition_category; /* Partition category */ + u32 partition_domain; /* Partition domain */ + u32 partition_type; /* Partition type */ + u32 num_partitions; /* Number of partitions on device */ + u32 hw_info; /* hw information such as type and frequency */ + u32 reserved4; /* Reserved for future use */ + u32 reserved5; /* Reserved for future use */ +}; + +/* + * Defines the RAM partition table structure (Version 1) + * + * Do not change the placement of the first four elements so that future + * compatibility will always be guaranteed at least for the identifiers. + * + * The other portion of the structure may be changed as necessary to accommodate + * new features. Be sure to increment version number if you change it. + */ +struct usable_ram_partition_table_v1 { + struct usable_ram_partition_table_header header; + + u32 reserved2; /* Added for 8 bytes alignment of header */ + + /* RAM partition table entries */ + struct ram_partition_entry_v1 entries[RAM_NUM_PART_ENTRIES]; +}; + +/* Version 0 structure 32 Bit - Holds information for an entry in the RAM partition table */ +struct ram_partition_entry_v0 { + char name[RAM_PART_NAME_LENGTH]; /* Partition name, unused for now */ + u32 start_address; /* Partition start address in RAM */ + u32 length; /* Partition length in RAM in Bytes */ + u32 partition_attribute; /* Partition attribute */ + u32 partition_category; /* Partition category */ + u32 partition_domain; /* Partition domain */ + u32 partition_type; /* Partition type */ + u32 num_partitions; /* Number of partitions on device */ + u32 reserved3; /* Reserved for future use */ + u32 reserved4; /* Reserved for future use */ + u32 reserved5; /* Reserved for future use */ +}; + +/* + * Defines the RAM partition table structure (Version 0) + * + * Do not change the placement of the first four elements so that future + * compatibility will always be guaranteed at least for the identifiers. + * + * The other portion of the structure may be changed as necessary to accommodate + * new features. Be sure to increment version number if you change it. + */ +struct usable_ram_partition_table_v0 { + struct usable_ram_partition_table_header header; + + /* RAM partition table entries */ + struct ram_partition_entry_v0 entries[RAM_NUM_PART_ENTRIES]; +}; + +#endif // __QCOM_SMEM_RAMPART_H__ From b89878b799fd3fd4938374b222f4d07cecf7b2f1 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:42 +0200 Subject: [PATCH 56/70] mach-snapdragon: fetch serial# from SMEM If available, otherwise fall back to cmdline. Signed-off-by: Casey Connolly --- arch/arm/mach-snapdragon/board.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-snapdragon/board.c index a8a85cd9aec5..00280d484c71 100644 --- a/arch/arm/mach-snapdragon/board.c +++ b/arch/arm/mach-snapdragon/board.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include "qcom-priv.h" @@ -254,9 +255,14 @@ static const char *get_cmdline(void) void qcom_set_serialno(void) { - const char *cmdline = get_cmdline(); + const char *cmdline; char serial[32]; + if (!qcom_socinfo_init()) + return; + + cmdline = get_cmdline(); + if (!cmdline) { log_debug("Failed to get bootargs\n"); return; @@ -476,6 +482,9 @@ int board_late_init(void) memcpy((void *)addr, (void *)gd->fdt_blob, fdt32_to_cpu(fdt_blob->totalsize)); + /* Initialise SMEM if it wasn't done already and ensure it's memory is mapped */ + qcom_smem_init(); + configure_env(); qcom_late_init(); From 56904a857cfb49e33bcbd8b046d6380bb5df4ad6 Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Mon, 4 May 2026 20:57:43 +0200 Subject: [PATCH 57/70] configs: add qcom_sm8650_defconfig and debug fragment Add a defconfig for running on sm8650 as the first stage bootloader and associated debug config fragment. Signed-off-by: Casey Connolly --- board/qualcomm/debug-sm8650.config | 5 +++++ configs/qcom_sm8650_defconfig | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 board/qualcomm/debug-sm8650.config create mode 100644 configs/qcom_sm8650_defconfig diff --git a/board/qualcomm/debug-sm8650.config b/board/qualcomm/debug-sm8650.config new file mode 100644 index 000000000000..8323bf25e9c1 --- /dev/null +++ b/board/qualcomm/debug-sm8650.config @@ -0,0 +1,5 @@ +CONFIG_DEBUG_UART=y +CONFIG_DEBUG_UART_ANNOUNCE=y +CONFIG_DEBUG_UART_BASE=0x89c000 +CONFIG_DEBUG_UART_MSM_GENI=y +CONFIG_DEBUG_UART_CLOCK=14745600 diff --git a/configs/qcom_sm8650_defconfig b/configs/qcom_sm8650_defconfig new file mode 100644 index 000000000000..e0a82c460337 --- /dev/null +++ b/configs/qcom_sm8650_defconfig @@ -0,0 +1,16 @@ +# Configuration for building U-Boot to be flashed +# to the uefi partition of QCM6490 dev boards with +# the "Linux Embedded" partition layout (which have +# a dedicated "uefi" partition for edk2/U-Boot) + +#include "qcom_defconfig" + +# Otherwise buildman thinks this isn't an ARM platform +CONFIG_ARM=y + +# Address where U-Boot will be loaded +CONFIG_TEXT_BASE=0xa7000000 +CONFIG_REMAKE_ELF=y + +CONFIG_DEFAULT_DEVICE_TREE="qcom/sm8650-hdk" + From 2536d796efb14a65d5d61bff54d587c18ba1d967 Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Tue, 5 May 2026 13:04:31 +0530 Subject: [PATCH 58/70] dts: qcs6490-rb3gen2-u-boot: drop memory override node With SMEM based memory information available, let's drop memory node U-Boot DT override for RB3Gen2. Signed-off-by: Sumit Garg --- arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi b/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi index b8deaa38f51f..3c0d61861c75 100644 --- a/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi +++ b/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi @@ -3,19 +3,6 @@ * Copyright (c) 2024 Linaro Ltd. */ / { - /* When running as the primary bootloader there is no prior - * stage to populate the memory layout for us. We *should* - * have two nodes here, but ABL does NOT like that. - * sooo we're stuck with this. - */ - memory@80000000 { - device_type = "memory"; - reg = <0 0x80000000 0 0x3A800000>, - <0 0xC0000000 0 0x01800000>, - <0 0xC3400000 0 0x3CC00000>, - <1 0x00000000 1 0x00000000>; - }; - reserved-memory { #address-cells = <2>; #size-cells = <2>; @@ -42,8 +29,3 @@ }; }; }; - -// RAM Entry 0 : Base 0x0080000000 Size 0x003A800000 -// RAM Entry 1 : Base 0x00C0000000 Size 0x0001800000 -// RAM Entry 2 : Base 0x00C3400000 Size 0x003CC00000 -// RAM Entry 3 : Base 0x0100000000 Size 0x0100000000 From 41e88b2c7f285efd003af7b215c54072c37dcbc1 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Tue, 5 May 2026 15:26:56 +0530 Subject: [PATCH 59/70] mach-snapdragon: Add FIT multi-DTB selection support Implement multi DTB selection from FIT images based on hardware detection via SMEM. The implementation provides: 1. Hardware Detection: Reads SoC parameters from SMEM including chip ID, version, platform ID, OEM variant, DDR size, and storage type from IMEM. 2. Metadata DTB Processing: Parses a metadata DTB (first image in FIT) to build a "bucket list" of hardware-specific node names that match the detected hardware parameters. 3. FIT Configuration Matching: Uses standard FIT mechanisms to find the configuration with the most matching tokens in its compatible string compared to the hardware-derived bucket list. 4. DTB Loading and Overlays: Loads the base DTB and applies any DTBOs specified in the selected configuration using standard FIT overlay application. 5. EFI Integration: Loads selected dtb from qclinux_fit.img and sets fdt_addr for use by the EFI boot flow. This enables multi DTB selection across hardware variants. Signed-off-by: Aswin Murugan --- arch/arm/mach-snapdragon/Kconfig | 8 + arch/arm/mach-snapdragon/Makefile | 1 + arch/arm/mach-snapdragon/board.c | 7 + arch/arm/mach-snapdragon/qcom_fit_multidtb.c | 1040 ++++++++++++++++++ arch/arm/mach-snapdragon/qcom_fit_multidtb.h | 190 ++++ 5 files changed, 1246 insertions(+) create mode 100644 arch/arm/mach-snapdragon/qcom_fit_multidtb.c create mode 100644 arch/arm/mach-snapdragon/qcom_fit_multidtb.h diff --git a/arch/arm/mach-snapdragon/Kconfig b/arch/arm/mach-snapdragon/Kconfig index d3de8693b5a8..0f03672ee6bc 100644 --- a/arch/arm/mach-snapdragon/Kconfig +++ b/arch/arm/mach-snapdragon/Kconfig @@ -42,4 +42,12 @@ config SYS_CONFIG_NAME Based on this option include/configs/.h header will be used for board configuration. +config QCOM_FIT_MULTIDTB + bool "Enable FIT multi-DTB selection for Qualcomm platforms" + depends on FIT + help + Enable FIT multi-DTB selection for Qualcomm platforms. + This allows U-Boot to select the appropriate device tree + from a FIT image. + endif diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-snapdragon/Makefile index e481e4f26e5c..63b6db05552f 100644 --- a/arch/arm/mach-snapdragon/Makefile +++ b/arch/arm/mach-snapdragon/Makefile @@ -5,3 +5,4 @@ obj-y += board.o dram.o obj-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += capsule_update.o obj-$(CONFIG_OF_LIVE) += of_fixup.o +obj-$(CONFIG_QCOM_FIT_MULTIDTB) += qcom_fit_multidtb.o diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-snapdragon/board.c index 00280d484c71..6acbdbb1f6a1 100644 --- a/arch/arm/mach-snapdragon/board.c +++ b/arch/arm/mach-snapdragon/board.c @@ -37,6 +37,7 @@ #include #include +#include "qcom_fit_multidtb.h" #include "qcom-priv.h" DECLARE_GLOBAL_DATA_PTR; @@ -492,6 +493,12 @@ int board_late_init(void) /* Configure the dfu_string for capsule updates */ qcom_configure_capsule_updates(); + /* Try FIT multi-DTB selection if enabled */ + if (IS_ENABLED(CONFIG_QCOM_FIT_MULTIDTB)) { + if (qcom_fit_multidtb_setup()) + log_debug("FIT multi-DTB selection not available or failed\n"); + } + return 0; } diff --git a/arch/arm/mach-snapdragon/qcom_fit_multidtb.c b/arch/arm/mach-snapdragon/qcom_fit_multidtb.c new file mode 100644 index 000000000000..4135ad4dbccf --- /dev/null +++ b/arch/arm/mach-snapdragon/qcom_fit_multidtb.c @@ -0,0 +1,1040 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm FIT Multi-DTB Selection + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * Automatic DTB selection from FIT images based on hardware detection via SMEM. + * Loads qclinux_fit.img from dtb partition, detects hardware parameters, + * and selects the best matching DTB configuration. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qcom_fit_multidtb.h" + +/* GPT partition name containing the FIT image */ +#define QCOM_FIT_PARTNAME "dtb_a" + +#define TCSR_SOC_HW_VERSION 0x01fc8000 +#define TCSR_MAJOR_VERSION_MASK 0x0000ff00 +#define TCSR_MAJOR_VERSION_SHIFT 8 +#define TCSR_MINOR_VERSION_MASK 0x000000ff +#define TCSR_MINOR_VERSION_SHIFT 0 + +#define lmb_alloc(size, addr) lmb_alloc_mem(LMB_MEM_ALLOC_ANY, SZ_2M, addr, size, LMB_NONE) + +/* Maximum values to match (SOC needs 2) */ +#define MAX_MATCH_VALUES 2 + +/* Metadata DTB node names */ +#define META_NODE_OEM "oem" +#define META_NODE_SOC "soc" +#define META_NODE_SOCVER "socver" +#define META_NODE_BOARD "board" +#define META_NODE_BOARDREV "boardrev" +#define META_NODE_SOC_SKU "soc-sku" +#define META_NODE_BOARD_SUBTYPE_PERIPHERAL "board-subtype-peripheral-subtype" +#define META_NODE_BOARD_SUBTYPE_STORAGE "board-subtype-storage-type" +#define META_NODE_BOARD_SUBTYPE_DDR_SIZE "board-subtype-memory-size" +#define META_NODE_SOFTSKU "softsku" + +/* Property names */ +#define PROP_OEM_ID "oem-id" +#define PROP_MSM_ID "msm-id" +#define PROP_SOCVER_ID "socver-id" +#define PROP_BOARD_ID "board-id" +#define PROP_BOARDREV_ID "boardrev-id" +#define PROP_BOARD_SUBTYPE "board-subtype" +#define PROP_SOFTSKU_ID "softsku-id" +#define PROP_COMPATIBLE "compatible" +#define PROP_FDT "fdt" +#define PROP_DATA "data" + +/** + * add_to_bucket() - Add a node name to the bucket list + * @name: Node name to add + * @name_len: Length of the name + * @bucket_head: Head of the bucket list + * + * Return: 0 on success, negative error code on failure + */ +static int add_to_bucket(const char *name, int name_len, struct list_head *bucket_head) +{ + struct bucket_node *node; + + node = malloc(sizeof(*node)); + if (!node) + return -ENOMEM; + + node->name = malloc(name_len + 1); + if (!node->name) { + free(node); + return -ENOMEM; + } + + strlcpy(node->name, name, name_len + 1); + + list_add_tail(&node->list, bucket_head); + + return 0; +} + +/** + * search_in_bucket() - Check if a name exists in the bucket list + * @name: Name to search for + * @bucket_head: Head of the bucket list + * + * Return: true if found, false otherwise + */ +static bool search_in_bucket(const char *name, struct list_head *bucket_head) +{ + struct bucket_node *node; + + list_for_each_entry(node, bucket_head, list) { + if (!strcmp(node->name, name)) + return true; + } + + return false; +} + +/** + * free_bucket_list() - Free all nodes in the bucket list + * @bucket_head: Head of the bucket list + */ +static void free_bucket_list(struct list_head *bucket_head) +{ + struct bucket_node *node, *tmp; + + list_for_each_entry_safe(node, tmp, bucket_head, list) { + list_del(&node->list); + free(node->name); + free(node); + } +} + +/** + * qcom_get_ddr_size_type() - Get DDR size type from gd->ram_size + * @ddr_type: Pointer to store DDR type + * + * Uses gd->ram_size which is already calculated by dram.c from SMEM/FDT + * during early boot (same value U-Boot prints as "DRAM: X GiB"). + * Avoids redundant SMEM re-parsing. + * + * Return: 0 on success + */ +static int qcom_get_ddr_size_type(u32 *ddr_type) +{ + u64 total_ddr_size = gd->ram_size; + + log_info("Total DDR Size: 0x%llx (%llu MB)\n", + total_ddr_size, total_ddr_size / SZ_1M); + + *ddr_type = 0; + if (total_ddr_size <= DDR_128MB) + *ddr_type = DDRTYPE_128MB; + else if (total_ddr_size <= DDR_256MB) + *ddr_type = DDRTYPE_256MB; + else if (total_ddr_size <= DDR_512MB) + *ddr_type = DDRTYPE_512MB; + else if (total_ddr_size <= DDR_1024MB) + *ddr_type = DDRTYPE_1024MB; + else if (total_ddr_size <= DDR_2048MB) + *ddr_type = DDRTYPE_2048MB; + else if (total_ddr_size <= DDR_3072MB) + *ddr_type = DDRTYPE_3072MB; + else if (total_ddr_size <= DDR_4096MB) + *ddr_type = DDRTYPE_4096MB; + + log_debug("DDR Type: %u\n", *ddr_type); + + return 0; +} + +/** + * qcom_get_imem_address() - Look up shared IMEM cookie address for this SoC + * + * Reads the root node compatible string from the device tree and searches + * the qcom_imem_table for a matching entry. + * + * Return: Physical address of shared IMEM cookie, or 0 if not found + */ +static uintptr_t qcom_get_imem_address(void) +{ + const struct qcom_imem_info *entry; + const char *soc_compat; + int len; + + soc_compat = fdt_getprop(gd->fdt_blob, 0, "compatible", &len); + if (!soc_compat) { + log_warning("Cannot read SoC compatible from DT\n"); + return 0; + } + + for (entry = qcom_imem_table; entry->compatible; entry++) { + if (fdt_stringlist_contains(soc_compat, len, entry->compatible)) + return entry->shared_imem_addr; + } + + log_warning("SoC not found in IMEM table\n"); + return 0; +} + +/** + * qcom_get_storage_type() - Detect storage type (UFS/EMMC/NAND) + * + * Reads the boot device type from the shared IMEM cookie structure populated + * by the bootloader. The shared IMEM address is looked up from a per-SoC + * table (qcom_imem_table) using the device tree compatible string. + * + * Falls back to UFS if the SoC is not in the table or if the IMEM cookie + * magic/version validation fails. + * + * Return: mem_card_type enum value (UFS/EMMC/NAND), or UFS as fallback + */ +static enum mem_card_type qcom_get_storage_type(void) +{ + struct boot_imem_cookie *imem; + uintptr_t shared_imem_addr; + + shared_imem_addr = qcom_get_imem_address(); + if (!shared_imem_addr) { + log_warning("SoC not in IMEM table, defaulting to UFS\n"); + return UFS; + } + + imem = (struct boot_imem_cookie *)shared_imem_addr; + + if (imem->shared_imem_magic != BOOT_SHARED_IMEM_MAGIC_NUM) { + log_warning("Invalid shared IMEM magic: 0x%x, defaulting to UFS\n", + imem->shared_imem_magic); + return UFS; + } + + if (imem->shared_imem_version < BOOT_SHARED_IMEM_VERSION_NUM) { + log_warning("Invalid shared IMEM version: %u, defaulting to UFS\n", + imem->shared_imem_version); + return UFS; + } + + log_info("Shared IMEM: magic=0x%x, version=%u, boot_device_type=%u\n", + imem->shared_imem_magic, imem->shared_imem_version, + imem->boot_device_type); + + switch (imem->boot_device_type) { + case UFS_FLASH: + return UFS; + case MMC_FLASH: + case SDC_FLASH: + return EMMC; + case NAND_FLASH: + return NAND; + default: + log_warning("Unknown shared IMEM boot device: %u, defaulting to UFS\n", + imem->boot_device_type); + return UFS; + } +} + +static u32 qcom_read_tcsr_soc_version(void) +{ + u32 reg_val = readl(TCSR_SOC_HW_VERSION); + u32 major = (reg_val & TCSR_MAJOR_VERSION_MASK) >> TCSR_MAJOR_VERSION_SHIFT; + u32 minor = (reg_val & TCSR_MINOR_VERSION_MASK) >> TCSR_MINOR_VERSION_SHIFT; + + return (major << 4) | minor; +} + +/** + * qcom_detect_hardware_params() - Detect all hardware parameters from SMEM + * @params: Pointer to hardware parameters structure + * + * Reads hardware information directly from SMEM using qcom_smem_get() and + * populates the qcom_hw_params structure with all data needed for DTB selection. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_detect_hardware_params(struct qcom_hw_params *params) +{ + struct socinfo *soc_info; + size_t size; + int ret; + u32 raw_version; + + memset(params, 0, sizeof(*params)); + + soc_info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID, &size); + if (IS_ERR_OR_NULL(soc_info)) { + log_err("Failed to get SOC info from SMEM\n"); + return -ENODEV; + } + + params->chip_id = le32_to_cpu(soc_info->id) & 0xffff; + + raw_version = le32_to_cpu(soc_info->plat_ver); + params->board_version = (SOCINFO_MAJOR(raw_version) << 4) | SOCINFO_MINOR(raw_version); + + params->chip_version = qcom_read_tcsr_soc_version(); + + params->platform = le32_to_cpu(soc_info->hw_plat); + params->subtype = le32_to_cpu(soc_info->hw_plat_subtype); + + if (le32_to_cpu(soc_info->fmt) >= 17) + params->oem_variant_id = le32_to_cpu(soc_info->oem_variant); + + if (le32_to_cpu(soc_info->fmt) >= 9) + params->foundry_id = le32_to_cpu(soc_info->foundry_id); + + ret = qcom_get_ddr_size_type(¶ms->ddr_size_type); + if (ret) + log_warning("Failed to get DDR size, defaulting to 0\n"); + + params->storage_type = qcom_get_storage_type(); + + log_info("Hardware Parameters:\n"); + log_info(" Chip ID: 0x%x\n", params->chip_id); + log_info(" Chip Version: 0x%x\n", params->chip_version); + log_info(" Board Version: 0x%x\n", params->board_version); + log_info(" Platform: 0x%x\n", params->platform); + log_info(" Subtype: 0x%x\n", params->subtype); + log_info(" OEM Variant ID: 0x%x\n", params->oem_variant_id); + log_info(" DDR Size Type: %u\n", params->ddr_size_type); + log_info(" Storage Type: %u\n", params->storage_type); + log_info(" Foundry ID: 0x%x\n", params->foundry_id); + + return 0; +} + +/** + * log_match_values() - Log matched hardware parameter values + * @log_type: Type label for logging (e.g., "SOC", "Board") + * @subnode_name: Name of the matched node + * @num_match_values: Number of values to log + * @match_values: Array of matched values + */ +static void log_match_values(const char *log_type, const char *subnode_name, + int num_match_values, const u32 *match_values) +{ + int i; + + log_info("Matched %s: %s (", log_type, subnode_name); + + for (i = 0; i < num_match_values; i++) { + if (i > 0) + log_info(", "); + log_info("val%d=0x%x", i + 1, match_values[i]); + } + + log_info(")\n"); +} + +/** + * process_node() - Generic metadata node processor + * @type: Type of node to process + * @metadata: Metadata DTB pointer + * @root_offset: Root node offset + * @params: Hardware parameters + * @bucket_head: Bucket list head + * + * Processes different types of nodes in the metadata DTB. Handles matching + * hardware parameters against DTB properties, with support for bit masking/shifting + * and fallback values. + * + * Return: 0 on success, -ENOENT if no match, other negative on error + */ +static int process_node(enum node_process_type type, + void *metadata, + int root_offset, + struct qcom_hw_params *params, + struct list_head *bucket_head) +{ + const char *node_name, *prop_name, *log_type; + const char *fallback; + const char *subnode_name; + int node_offset, subnode, len, name_len; + int num_match_values, i; + u32 match_values[MAX_MATCH_VALUES]; + u32 masks[MAX_MATCH_VALUES]; + int shifts[MAX_MATCH_VALUES]; + bool all_match; + + fallback = NULL; + num_match_values = 1; + memset(shifts, 0, sizeof(shifts)); + memset(masks, 0xff, sizeof(masks)); + + switch (type) { + case NODE_TYPE_OEM: + node_name = META_NODE_OEM; + prop_name = PROP_OEM_ID; + match_values[0] = params->oem_variant_id; + log_type = "OEM"; + fallback = "qcom"; + break; + case NODE_TYPE_SOC: + node_name = META_NODE_SOC; + prop_name = PROP_MSM_ID; + match_values[0] = params->chip_id; + masks[0] = 0xffff; + num_match_values = 1; + log_type = "SOC"; + break; + case NODE_TYPE_SOCVER: + node_name = META_NODE_SOCVER; + prop_name = PROP_SOCVER_ID; + match_values[0] = params->chip_version; + num_match_values = 1; + log_type = "SOCVER"; + break; + case NODE_TYPE_BOARD: + node_name = META_NODE_BOARD; + prop_name = PROP_BOARD_ID; + match_values[0] = params->platform; + log_type = "Board"; + break; + case NODE_TYPE_BOARDREV: + node_name = META_NODE_BOARDREV; + prop_name = PROP_BOARDREV_ID; + match_values[0] = params->board_version; + num_match_values = 1; + log_type = "BoardRev"; + break; + case NODE_TYPE_PERIPHERAL: + node_name = META_NODE_BOARD_SUBTYPE_PERIPHERAL; + prop_name = PROP_BOARD_SUBTYPE; + match_values[0] = params->subtype; + log_type = "Peripheral Subtype"; + break; + case NODE_TYPE_STORAGE: + node_name = META_NODE_BOARD_SUBTYPE_STORAGE; + prop_name = PROP_BOARD_SUBTYPE; + match_values[0] = params->storage_type; + masks[0] = 0x7000; + shifts[0] = 12; + log_type = "Storage"; + break; + case NODE_TYPE_DDR_SIZE: + node_name = META_NODE_BOARD_SUBTYPE_DDR_SIZE; + prop_name = PROP_BOARD_SUBTYPE; + match_values[0] = params->ddr_size_type; + masks[0] = 0xf00; + shifts[0] = 8; + log_type = "DDR Size"; + break; + case NODE_TYPE_SOFTSKU: + node_name = META_NODE_SOFTSKU; + prop_name = PROP_SOFTSKU_ID; + match_values[0] = params->softsku_id; + log_type = "SoftSKU"; + break; + default: + return -EINVAL; + } + + node_offset = fdt_subnode_offset(metadata, root_offset, node_name); + if (node_offset < 0) { + log_debug("%s node not found\n", log_type); + return node_offset; + } + + fdt_for_each_subnode(subnode, metadata, node_offset) { + const u32 *prop = fdt_getprop(metadata, subnode, prop_name, &len); + + if (!prop || len < (int)(num_match_values * sizeof(u32))) + continue; + + all_match = true; + for (i = 0; i < num_match_values; i++) { + u32 dtb_value = fdt32_to_cpu(prop[i]); + + dtb_value = (dtb_value & masks[i]) >> shifts[i]; + + if (dtb_value != match_values[i]) { + all_match = false; + break; + } + } + + if (!all_match) + continue; + + subnode_name = fdt_get_name(metadata, subnode, &name_len); + if (subnode_name) { + log_match_values(log_type, subnode_name, num_match_values, + match_values); + return add_to_bucket(subnode_name, name_len, bucket_head); + } + } + + if (fallback) { + log_info("No %s match, using fallback '%s'\n", log_type, fallback); + return add_to_bucket(fallback, strlen(fallback), bucket_head); + } + + log_debug("No %s match\n", log_type); + + return -ENOENT; +} + +/** + * qcom_build_bucket_list() - Build bucket list from metadata DTB + * @metadata: Metadata DTB pointer + * @params: Hardware parameters + * @bucket_head: Bucket list head + * + * This function parses the metadata DTB and builds a list of matching + * node names based on the detected hardware parameters. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_build_bucket_list(void *metadata, + struct qcom_hw_params *params, + struct list_head *bucket_head) +{ + int root_offset; + int ret; + struct bucket_node *node; + + log_debug("Building bucket list from hardware parameters\n"); + + root_offset = fdt_path_offset(metadata, "/"); + if (root_offset < 0) { + log_err("Failed to find root node in metadata DTB\n"); + return root_offset; + } + + ret = process_node(NODE_TYPE_OEM, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + ret = process_node(NODE_TYPE_SOC, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + ret = process_node(NODE_TYPE_SOCVER, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + ret = process_node(NODE_TYPE_BOARD, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + ret = process_node(NODE_TYPE_BOARDREV, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + process_node(NODE_TYPE_PERIPHERAL, metadata, root_offset, + params, bucket_head); + + process_node(NODE_TYPE_STORAGE, metadata, root_offset, + params, bucket_head); + + process_node(NODE_TYPE_DDR_SIZE, metadata, root_offset, + params, bucket_head); + + process_node(NODE_TYPE_SOFTSKU, metadata, root_offset, + params, bucket_head); + + log_debug("Bucket list: "); + list_for_each_entry(node, bucket_head, list) + log_debug("%s ", node->name); + log_debug("\n"); + + return 0; +} + +/** + * qcom_load_raw_partition() - Load FIT image from a raw partition by name + * @partname: GPT partition name to search for + * @fitp: Pointer to store FIT image address + * @fit_sizep: Pointer to store FIT image size + * + * Searches all block devices for a GPT partition with the given name and + * reads the entire partition into a buffer. The caller is responsible for + * validating the content (fdt_check_header, fit_check_format). + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_load_raw_partition(const char *partname, void **fitp, + size_t *fit_sizep) +{ + struct udevice *blk_dev; + struct blk_desc *desc = NULL; + struct disk_partition part_info; + void *part_buf; + size_t part_size; + + blk_foreach_probe(BLKF_BOTH, blk_dev) { + desc = dev_get_uclass_plat(blk_dev); + if (!desc) + continue; + if (part_get_info_by_name(desc, partname, &part_info) >= 0) + break; + desc = NULL; + } + + if (!desc) { + log_err("Partition '%s' not found on any block device\n", partname); + return -ENOENT; + } + + part_size = (size_t)(part_info.size * part_info.blksz); + part_buf = malloc(part_size); + if (!part_buf) + return -ENOMEM; + + if (blk_dread(desc, part_info.start, part_info.size, part_buf) != part_info.size) { + log_err("Failed to read partition '%s'\n", partname); + free(part_buf); + return -EIO; + } + + *fitp = part_buf; + *fit_sizep = part_size; + + log_info("Loaded raw partition '%s': %zu bytes\n", partname, part_size); + return 0; +} + +/** + * qcom_extract_metadata_dtb() - Extract metadata DTB from FIT image + * @fit: FIT image pointer + * @metadata: Pointer to store metadata DTB address + * @metadata_size: Pointer to store metadata DTB size + * + * The metadata DTB is the first image in the FIT (fdt-0). + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_extract_metadata_dtb(void *fit, void **metadata, + size_t *metadata_size) +{ + int images_node, first_image; + const void *data; + size_t size; + int ret; + + images_node = fdt_path_offset(fit, FIT_IMAGES_PATH); + if (images_node < 0) { + log_err("Cannot find /images node in FIT\n"); + return images_node; + } + + first_image = fdt_first_subnode(fit, images_node); + if (first_image < 0) { + log_err("Cannot find first image in FIT\n"); + return first_image; + } + + ret = fit_image_get_data(fit, first_image, &data, &size); + if (ret) { + log_err("Failed to get metadata DTB data\n"); + return ret; + } + + *metadata = malloc(size); + if (!*metadata) { + log_err("Failed to allocate memory for metadata DTB\n"); + return -ENOMEM; + } + + memcpy(*metadata, data, size); + *metadata_size = size; + + log_info("Extracted metadata DTB: %zu bytes\n", size); + + return 0; +} + +/** + * qcom_count_compatible_matches() - Count matching tokens in compatible string + * @compatible: Compatible string from FIT configuration + * @compat_len: Length of compatible string + * @bucket_head: Bucket list head + * + * Parses the compatible string and counts how many tokens match entries + * in the bucket list. The compatible string format is typically: + * "vendor,device-variant-subtype" where tokens are separated by commas and dashes. + * + * Return: Number of matching tokens + */ +static int qcom_count_compatible_matches(const char *compatible, int compat_len, + struct list_head *bucket_head) +{ + char *compat_copy; + char *str_ptr; + char *token; + int match_count = 0; + + compat_copy = malloc(compat_len + 1); + if (!compat_copy) + return 0; + + memcpy(compat_copy, compatible, compat_len); + compat_copy[compat_len] = '\0'; + + str_ptr = compat_copy; + + /* First split by comma to get vendor prefix (e.g., "qcom") */ + token = strsep(&str_ptr, ","); + if (token && search_in_bucket(token, bucket_head)) + match_count++; + + /* Then split remaining parts by dash */ + token = strsep(&str_ptr, "-"); + while (token) { + if (search_in_bucket(token, bucket_head)) + match_count++; + token = strsep(&str_ptr, "-"); + } + + free(compat_copy); + return match_count; +} + +/** + * qcom_find_matching_config() - Find matching FIT configuration + * @fit: FIT image pointer + * @bucket_head: Bucket list head + * @config_node: Pointer to store matching configuration node offset + * + * This function iterates through all FIT configurations and finds the one + * with the most matching tokens in its compatible string against the bucket list. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_find_matching_config(void *fit, struct list_head *bucket_head, + int *config_node) +{ + int configs_node, cfg; + const char *compatible; + int compat_len; + const char *cfg_name; + int name_len; + int best_match_count = 0; + int best_config = -1; + int match_count; + + configs_node = fdt_path_offset(fit, FIT_CONFS_PATH); + if (configs_node < 0) { + log_err("Cannot find /configurations node in FIT\n"); + return configs_node; + } + + fdt_for_each_subnode(cfg, fit, configs_node) { + cfg_name = fdt_get_name(fit, cfg, &name_len); + compatible = fdt_getprop(fit, cfg, PROP_COMPATIBLE, &compat_len); + + if (!compatible || compat_len <= 0) { + log_debug("Config %s has no compatible property\n", cfg_name); + continue; + } + + log_debug("Checking config: %s, compatible: %s\n", + cfg_name, compatible); + + match_count = qcom_count_compatible_matches(compatible, compat_len, + bucket_head); + + log_debug("Config %s: %d matches\n", cfg_name, match_count); + + if (match_count > best_match_count) { + best_match_count = match_count; + best_config = cfg; + } + } + + if (best_config < 0) { + log_err("No matching configuration found\n"); + return -ENOENT; + } + + cfg_name = fdt_get_name(fit, best_config, &name_len); + compatible = fdt_getprop(fit, best_config, PROP_COMPATIBLE, &compat_len); + log_info("Selected configuration: %s (compatible: %s, matches: %d)\n", + cfg_name, compatible, best_match_count); + + *config_node = best_config; + return 0; +} + +/** + * qcom_get_fdt_image_data() - Get FDT image data from FIT + * @fit: FIT image pointer + * @images_node: Images node offset + * @fdt_name: FDT image name to load + * @fdt_datap: Pointer to store FDT data address + * @fdt_sizep: Pointer to store FDT data size + * + * Helper function to load an FDT image from the FIT by name. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_get_fdt_image_data(void *fit, int images_node, + const char *fdt_name, + const void **fdt_datap, size_t *fdt_sizep) +{ + int fdt_node; + int ret; + + fdt_node = fdt_subnode_offset(fit, images_node, fdt_name); + if (fdt_node < 0) { + log_err("Cannot find FDT node: %s\n", fdt_name); + return fdt_node; + } + + ret = fit_image_get_data(fit, fdt_node, fdt_datap, fdt_sizep); + if (ret) { + log_err("Failed to get FDT data for %s\n", fdt_name); + return ret; + } + + return 0; +} + +/** + * qcom_load_dtb_with_overlays() - Load DTB and apply overlays + * @fit: FIT image pointer + * @config_node: Configuration node offset + * @final_dtb: Pointer to store final DTB address + * @final_dtb_size: Pointer to store final DTB size + * + * This function loads the base DTB and applies all DTBOs specified in the + * configuration's "fdt" property. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_load_dtb_with_overlays(void *fit, int config_node, + void **final_dtb, + size_t *final_dtb_size) +{ + int images_node; + const char *fdt_name; + int fdt_name_len; + const void *fdt_data; + size_t fdt_size; + void *base_dtb = NULL; + size_t base_dtb_size = 0; + phys_addr_t dtb_addr; + int i, ret; + int fixups_offset; + + images_node = fdt_path_offset(fit, FIT_IMAGES_PATH); + if (images_node < 0) + return images_node; + + fdt_name = fdt_stringlist_get(fit, config_node, PROP_FDT, 0, &fdt_name_len); + if (!fdt_name) { + log_err("No fdt property in configuration\n"); + return -EINVAL; + } + + log_info("DTB: %s\n", fdt_name); + + ret = qcom_get_fdt_image_data(fit, images_node, fdt_name, + &fdt_data, &fdt_size); + if (ret) + return ret; + + /* Allocate base DTB with extra space for overlays using LMB */ + base_dtb_size = fdt_size + (8 * 1024); /* Add 8KB for overlays */ + ret = lmb_alloc(base_dtb_size, &dtb_addr); + if (ret) { + log_err("Failed to allocate LMB memory for base DTB: %zu bytes\n", base_dtb_size); + return -ENOMEM; + } + base_dtb = (void *)dtb_addr; + + memcpy(base_dtb, fdt_data, fdt_size); + ret = fdt_open_into(base_dtb, base_dtb, base_dtb_size); + if (ret) { + log_err("Failed to open DTB: %d\n", ret); + return ret; + } + + /* Apply overlays (remaining fdt entries) */ + for (i = 1; ; i++) { + fdt_name = fdt_stringlist_get(fit, config_node, PROP_FDT, i, + &fdt_name_len); + if (!fdt_name) + break; + + log_info("Applying overlay: %s\n", fdt_name); + + ret = qcom_get_fdt_image_data(fit, images_node, fdt_name, + &fdt_data, &fdt_size); + if (ret) + continue; + + fixups_offset = fdt_path_offset(fdt_data, "/__fixups__"); + if (fixups_offset == -FDT_ERR_NOTFOUND) { + log_warning("%s is not a valid overlay (no __fixups__)\n", fdt_name); + continue; + } + + ret = fdt_overlay_apply_verbose(base_dtb, (void *)fdt_data); + if (ret) + log_err("Failed to apply overlay %s: %d\n", fdt_name, ret); + } + + ret = fdt_pack(base_dtb); + if (ret) { + log_err("Failed to pack DTB: %d\n", ret); + return ret; + } + + *final_dtb = base_dtb; + *final_dtb_size = fdt_totalsize(base_dtb); + + log_info("Final DTB size: %zu bytes\n", *final_dtb_size); + + return 0; +} + +/** + * qcom_fit_multidtb_setup() - Main entry point for FIT multi-DTB selection + * + * This is the main function that orchestrates the entire DTB selection process: + * 1. Load qclinux_fit.img from EFI partition + * 2. Extract metadata DTB + * 3. Detect hardware parameters from SMEM + * 4. Build bucket list from metadata + * 5. Find matching FIT configuration + * 6. Load DTB and apply overlays + * 7. Install FDT for EFI + * + * Return: 0 on success, negative error code on failure + */ +int qcom_fit_multidtb_setup(void) +{ + void *fit = NULL; + size_t fit_size = 0; + void *metadata = NULL; + size_t metadata_size = 0; + struct qcom_hw_params hw_params; + LIST_HEAD(bucket_list); + int config_node; + void *final_dtb = NULL; + size_t final_dtb_size = 0; + int ret; + + log_debug("=== FIT Multi-DTB Selection ===\n"); + + log_debug("Loading FIT image\n"); + ret = qcom_load_raw_partition(QCOM_FIT_PARTNAME, &fit, &fit_size); + if (ret) { + log_err("Failed to load FIT image\n"); + goto cleanup_fit; + } + + ret = fdt_check_header(fit); + if (ret) { + log_err("Invalid FIT header\n"); + ret = -EINVAL; + goto cleanup_fit; + } + + ret = fit_check_format(fit, IMAGE_SIZE_INVAL); + if (ret) { + log_err("Invalid FIT format\n"); + ret = -EINVAL; + goto cleanup_fit; + } + + log_debug("Extracting metadata DTB\n"); + ret = qcom_extract_metadata_dtb(fit, &metadata, &metadata_size); + if (ret) { + log_err("Failed to extract metadata DTB\n"); + goto cleanup_metadata; + } + + log_debug("Detecting hardware parameters\n"); + ret = qcom_detect_hardware_params(&hw_params); + if (ret) { + log_err("Failed to detect hardware parameters\n"); + goto cleanup_metadata; + } + + log_debug("Building bucket list\n"); + ret = qcom_build_bucket_list(metadata, &hw_params, &bucket_list); + if (ret) { + log_err("Failed to build bucket list\n"); + goto cleanup_bucket; + } + + log_debug("Finding matching configuration\n"); + ret = qcom_find_matching_config(fit, &bucket_list, &config_node); + if (ret) { + log_err("Failed to find matching configuration\n"); + goto cleanup_bucket; + } + + log_debug("Loading DTB and applying overlays\n"); + ret = qcom_load_dtb_with_overlays(fit, config_node, &final_dtb, + &final_dtb_size); + if (ret) { + log_err("Failed to load DTB with overlays\n"); + goto cleanup_dtb; + } + + log_debug("Setting fdt_addr to selected DTB address\n"); + + ret = fdt_check_header(final_dtb); + if (ret) { + log_err("Invalid final DTB header: %d\n", ret); + ret = -EINVAL; + goto cleanup_dtb; + } + + /* Update fdt_addr environment variable to point to our DTB */ + env_set_hex("fdt_addr", (ulong)final_dtb); + log_info("Updated fdt_addr=0x%lx, DTB size=%zu bytes\n", (ulong)final_dtb, final_dtb_size); + log_info("EFI boot flow will use DTB directly from this address\n"); + + /* Don't free final_dtb - LMB manages memory and EFI boot flow will use it */ + final_dtb = NULL; + + log_debug("=== FIT Multi-DTB Selection Complete ===\n"); + + ret = 0; + goto cleanup_success; + +cleanup_dtb: + if (ret && final_dtb) + final_dtb = NULL; + +cleanup_success: +cleanup_bucket: + free_bucket_list(&bucket_list); + +cleanup_metadata: + if (metadata) + free(metadata); + +cleanup_fit: + if (fit) + free(fit); + + return ret; +} diff --git a/arch/arm/mach-snapdragon/qcom_fit_multidtb.h b/arch/arm/mach-snapdragon/qcom_fit_multidtb.h new file mode 100644 index 000000000000..802eb749fe80 --- /dev/null +++ b/arch/arm/mach-snapdragon/qcom_fit_multidtb.h @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Qualcomm FIT Multi-DTB Selection + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * This implements automatic DTB selection from FIT images based on hardware + * detection via SMEM. + */ + +#ifndef __QCOM_FIT_MULTIDTB_H__ +#define __QCOM_FIT_MULTIDTB_H__ + +#include +#include +#include + +/* DDR size thresholds */ +#define DDR_128MB SZ_128M +#define DDR_256MB SZ_256M +#define DDR_512MB SZ_512M +#define DDR_1024MB SZ_1G +#define DDR_2048MB SZ_2G +#define DDR_3072MB (SZ_2G + SZ_1G) +#define DDR_4096MB SZ_4G + +/* DDR type enum */ +enum ddr_type { + DDRTYPE_256MB = 1, + DDRTYPE_512MB, /* 2 */ + DDRTYPE_1024MB, /* 3 */ + DDRTYPE_2048MB, /* 4 */ + DDRTYPE_3072MB, /* 5 */ + DDRTYPE_4096MB, /* 6 */ + DDRTYPE_128MB, /* 7 */ +}; + +/* Storage type enum */ +enum mem_card_type { + UFS = 0, + EMMC = 1, + NAND = 2, + STORAGE_UNKNOWN, +}; + +/* Boot device types from shared IMEM */ +enum boot_media_type { + NO_FLASH = 0, + NOR_FLASH = 1, + NAND_FLASH = 2, + ONENAND_FLASH = 3, + SDC_FLASH = 4, + MMC_FLASH = 5, + SPI_FLASH = 6, + PCIE_FLASHLESS = 7, + UFS_FLASH = 8, + RESERVED_0_FLASH = 9, + RESERVED_1_FLASH = 10, + USB_FLASHLESS = 11 +}; + +/* Shared IMEM constants */ +#define BOOT_SHARED_IMEM_MAGIC_NUM 0xc1f8db40 +#define BOOT_SHARED_IMEM_VERSION_NUM 0x3 + +/** + * struct qcom_imem_info - SoC-specific shared IMEM cookie address mapping + * @compatible: SoC compatible string (e.g., "qcom,qcs6490") + * @shared_imem_addr: Physical address of the boot shared IMEM cookie + * + * The shared IMEM cookie is populated by the bootloader and contains + * boot device type and other boot parameters. Its location varies per SoC + * and is calculated as: SCL_IMEM_BASE + IMEM_SIZE - 0x1000 (4KB cookie). + * Only SoCs with CONFIG_QCOM_FIT_MULTIDTB support are listed here. + */ +struct qcom_imem_info { + const char *compatible; + uintptr_t shared_imem_addr; +}; + +/* + * Per-SoC shared IMEM cookie address table. + * Address = SCL_IMEM_BASE(0x14680000) + IMEM_SIZE - 0x1000 + * QCM6490/QCS6490/QCS615: IMEM_SIZE=0x2B000 -> 0x146aa000 + * QCS9100: IMEM_SIZE=0x59000 -> 0x146d8000 + */ +static const struct qcom_imem_info qcom_imem_table[] = { + { "qcom,qcm6490", 0x146aa000 }, + { "qcom,qcs6490", 0x146aa000 }, + { "qcom,qcs615", 0x146aa000 }, + { "qcom,qcs9100", 0x146d8000 }, + { } +}; + +/* Boot shared IMEM cookie structure */ +struct boot_imem_cookie { + u32 shared_imem_magic; + u32 shared_imem_version; + u64 etb_buf_addr; + u64 l2_cache_dump_buff_addr; + u32 a64_pointer_padding; + u32 uefi_ram_dump_magic; + u32 ddr_training_cookie; + u32 abnormal_reset_occurred; + u32 reset_status_register; + u32 rpm_sync_cookie; + u32 debug_config; + u64 boot_log_addr; + u32 boot_log_size; + u32 boot_fail_count; + u32 sbl1_error_type; + u32 uefi_image_magic; + u32 boot_device_type; + u64 boot_devtree_addr; + u64 boot_devtree_size; +}; + +/** + * struct qcom_hw_params - Hardware parameters detected from SMEM + * @chip_id: SoC chip ID (from socinfo->id) + * @chip_version: SoC version (from TCSR register) + * @platform: Hardware platform ID (from socinfo->hw_plat) + * @board_version: Board version (from socinfo->plat_ver) + * @subtype: Hardware platform subtype (from socinfo->hw_plat_subtype) + * @oem_variant_id: OEM variant ID (from socinfo->oem_variant) + * @ddr_size_type: DDR size type (0-10, calculated from RAM partitions) + * @storage_type: Storage type (UFS=0, EMMC=1, NAND=2) + * @foundry_id: Foundry ID (from socinfo->foundry_id) + * @softsku_id: Software SKU ID (if available) + * + * This structure holds all hardware parameters needed for DTB selection. + */ +struct qcom_hw_params { + u32 chip_id; + u32 chip_version; + u32 platform; + u32 board_version; + u32 subtype; + u32 oem_variant_id; + u32 ddr_size_type; + u32 storage_type; + u32 foundry_id; + u32 softsku_id; +}; + +/** + * struct bucket_node - Node in the bucket list + * @list: List head for linking nodes + * @name: Node name string (e.g., "qcom", "sa8775p-v2", "ride", "ufs", "8gb") + * + * The bucket list contains all matching node names from the metadata DTB. + * These are used to match against FIT configuration compatible strings. + */ +struct bucket_node { + struct list_head list; + char *name; +}; + +/* Node processing types for metadata DTB parsing */ +enum node_process_type { + NODE_TYPE_OEM, + NODE_TYPE_SOC, + NODE_TYPE_SOCVER, + NODE_TYPE_BOARD, + NODE_TYPE_BOARDREV, + NODE_TYPE_PERIPHERAL, + NODE_TYPE_STORAGE, + NODE_TYPE_DDR_SIZE, + NODE_TYPE_SOFTSKU, +}; + +/* Function prototypes */ + +/** + * qcom_fit_multidtb_setup() - Main entry point for FIT multi-DTB selection + * + * This function: + * 1. Loads qclinux_fit.img from EFI partition + * 2. Extracts metadata DTB + * 3. Detects hardware parameters from SMEM + * 4. Builds bucket list from metadata + * 5. Finds matching FIT configuration + * 6. Loads DTB and applies overlays + * 7. Sets FDT for EFI + * + * Return: 0 on success, negative error code on failure + */ +int qcom_fit_multidtb_setup(void); + +#endif /* __QCOM_FIT_MULTIDTB_H__ */ From 9694700ed0bceac90d7181783239528a3b393664 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Tue, 5 May 2026 15:34:24 +0530 Subject: [PATCH 60/70] mach-snapdragon: Add FAT loading path as EDK2 workaround EDK2-based bootloaders on Qualcomm platforms place the FIT image (qclinux_fit.img) on a FAT filesystem partition rather than a raw GPT partition. Add a FAT loading path as a workaround to maintain compatibility with such bootloaders. This FAT loading path is a temporary workaround and will be removed once EDK2 and other bootloaders are updated to place the FIT image in the raw dtb partitions. Signed-off-by: Aswin Murugan --- arch/arm/mach-snapdragon/qcom_fit_multidtb.c | 96 +++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-snapdragon/qcom_fit_multidtb.c b/arch/arm/mach-snapdragon/qcom_fit_multidtb.c index 4135ad4dbccf..cc0321201a11 100644 --- a/arch/arm/mach-snapdragon/qcom_fit_multidtb.c +++ b/arch/arm/mach-snapdragon/qcom_fit_multidtb.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,8 @@ #include "qcom_fit_multidtb.h" -/* GPT partition name containing the FIT image */ +/* FIT image filename on FAT partition and raw partition name */ +#define QCOM_FIT_FILENAME "qclinux_fit.img" #define QCOM_FIT_PARTNAME "dtb_a" #define TCSR_SOC_HW_VERSION 0x01fc8000 @@ -564,6 +566,45 @@ static int qcom_build_bucket_list(void *metadata, return 0; } +/** + * qcom_find_fat_file() - Find a file on any FAT partition across all block devices + * @filename: Filename to search for + * @descp: Returns block descriptor of the partition containing the file + * @part_info: Returns partition info of the found partition + * + * Iterates through all block devices and their partitions, mounting each as + * FAT and checking if the file exists. + * + * Return: 0 on success, -ENOENT if not found + */ +static int qcom_find_fat_file(const char *filename, struct blk_desc **descp, + struct disk_partition *part_info) +{ + struct udevice *blk_dev; + struct blk_desc *desc; + loff_t file_size; + int partnum; + + blk_foreach_probe(BLKF_BOTH, blk_dev) { + desc = dev_get_uclass_plat(blk_dev); + if (!desc) + continue; + + for (partnum = 1; partnum <= MAX_SEARCH_PARTITIONS; partnum++) { + if (part_get_info(desc, partnum, part_info)) + break; + if (fat_set_blk_dev(desc, part_info)) + continue; + if (!fat_size(filename, &file_size)) { + *descp = desc; + return 0; + } + } + } + + return -ENOENT; +} + /** * qcom_load_raw_partition() - Load FIT image from a raw partition by name * @partname: GPT partition name to search for @@ -617,6 +658,56 @@ static int qcom_load_raw_partition(const char *partname, void **fitp, return 0; } +/** + * qcom_load_fit_image() - Load FIT image from FAT partition or raw partition + * @filename: Filename to search for on FAT partitions + * @partname: GPT partition name to use as fallback (raw image) + * @fitp: Pointer to store FIT image address + * @fit_sizep: Pointer to store FIT image size + * + * First tries to find @filename on any FAT partition across all block devices. + * If not found, falls back to reading the raw partition named @partname. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_load_fit_image(const char *filename, const char *partname, + void **fitp, size_t *fit_sizep) +{ + struct blk_desc *desc; + struct disk_partition part_info; + loff_t file_size; + void *fit_buf; + int ret; + + log_info("%s: Loading FIT image\n", __func__); + + /* Try FAT first: search for filename on any FAT partition */ + ret = qcom_find_fat_file(filename, &desc, &part_info); + if (!ret) { + fat_size(filename, &file_size); + + fit_buf = malloc(file_size); + if (!fit_buf) + return -ENOMEM; + + ret = file_fat_read(filename, fit_buf, file_size); + if (ret >= 0) { + log_info("Loaded '%s' from FAT partition: %lld bytes\n", + filename, file_size); + *fitp = fit_buf; + *fit_sizep = file_size; + return 0; + } + + log_debug("FAT read of '%s' failed (%d), trying raw partition\n", + filename, ret); + free(fit_buf); + } + + /* Fallback: try raw partition by name */ + return qcom_load_raw_partition(partname, fitp, fit_sizep); +} + /** * qcom_extract_metadata_dtb() - Extract metadata DTB from FIT image * @fit: FIT image pointer @@ -942,7 +1033,8 @@ int qcom_fit_multidtb_setup(void) log_debug("=== FIT Multi-DTB Selection ===\n"); log_debug("Loading FIT image\n"); - ret = qcom_load_raw_partition(QCOM_FIT_PARTNAME, &fit, &fit_size); + ret = qcom_load_fit_image(QCOM_FIT_FILENAME, QCOM_FIT_PARTNAME, + &fit, &fit_size); if (ret) { log_err("Failed to load FIT image\n"); goto cleanup_fit; From 60cae35a21f8b2dec710e45d4bb8ab4c7516fd8a Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 17 Apr 2026 17:39:47 +0530 Subject: [PATCH 61/70] configs: snapdragon: Enable FIT multi-DTB support Enable CONFIG_QCOM_FIT_MULTIDTB by default for Qualcomm Snapdragon platforms to provide automatic DTB selection from qclinux_fit.img with overlay support. This allows U-Boot to automatically select the appropriate device tree based on hardware parameters detected from SMEM, improving boot compatibility across different hardware variants and reducing the need for board-specific DTB selection logic. Reviewed-by: Simon Glass Signed-off-by: Aswin Murugan --- configs/qcom_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configs/qcom_defconfig b/configs/qcom_defconfig index af2b8f8e973e..2f3c8f0cee51 100644 --- a/configs/qcom_defconfig +++ b/configs/qcom_defconfig @@ -13,6 +13,7 @@ CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y CONFIG_BUTTON_CMD=y CONFIG_FIT=y CONFIG_FIT_VERBOSE=y +CONFIG_QCOM_FIT_MULTIDTB=y CONFIG_BOOTSTD_FULL=y # CONFIG_BOOTMETH_VBE is not set CONFIG_BOOTDELAY=1 @@ -49,6 +50,7 @@ CONFIG_CMD_RNG=y CONFIG_CMD_REGULATOR=y CONFIG_CMD_LOG=y CONFIG_OF_LIVE=y +CONFIG_OF_LIBFDT_OVERLAY=y CONFIG_OF_UPSTREAM_BUILD_VENDOR=y CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE=y CONFIG_ENV_DEFAULT_ENV_TEXT_FILE="board/qualcomm/default.env" From 74031b315b00c240f89fc1ceb4c7f603e3f7094e Mon Sep 17 00:00:00 2001 From: Sumit Garg Date: Thu, 7 May 2026 12:02:50 +0530 Subject: [PATCH 62/70] armv8: mmu: Extend mmu_map_region() to allow uncached mappings On Qcom platforms, cmd-db and SMEM regions are required to be mapped as uncached regions especially when executing at EL2. Otherwise in case of CMD-DB, an xPU violation can be triggered due to cache operations as descibed by a similar fix for the kernel here: Commit f9bb896eab22 ("soc: qcom: cmd-db: Map shared memory as WC, not WB") So, let's map CMD-DB and SMEM regions as unmapped to avoid any issues while booting U-Boot in EL2 mode. Signed-off-by: Sumit Garg --- arch/arm/cpu/armv8/cache_v8.c | 11 ++++++++--- arch/arm/include/asm/system.h | 3 ++- drivers/soc/qcom/cmd-db.c | 2 +- drivers/soc/qcom/smem.c | 5 +++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/arch/arm/cpu/armv8/cache_v8.c b/arch/arm/cpu/armv8/cache_v8.c index b6506e8523be..72c6eb200f2e 100644 --- a/arch/arm/cpu/armv8/cache_v8.c +++ b/arch/arm/cpu/armv8/cache_v8.c @@ -388,13 +388,18 @@ static void map_range(u64 virt, u64 phys, u64 size, int level, } } -void mmu_map_region(phys_addr_t addr, u64 size, bool emergency) +void mmu_map_region(phys_addr_t addr, u64 size, bool cached, bool emergency) { u64 va_bits; int level = 0; - u64 attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) | PTE_BLOCK_INNER_SHARE; + u64 attrs; - attrs |= PTE_TYPE_BLOCK | PTE_BLOCK_AF; + if (cached) + attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL); + else + attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL_NC); + + attrs |= PTE_BLOCK_INNER_SHARE | PTE_TYPE_BLOCK | PTE_BLOCK_AF; get_tcr(NULL, &va_bits); if (va_bits < 39) diff --git a/arch/arm/include/asm/system.h b/arch/arm/include/asm/system.h index 5ed6833c155c..52ab86112fb1 100644 --- a/arch/arm/include/asm/system.h +++ b/arch/arm/include/asm/system.h @@ -306,9 +306,10 @@ void flush_l3_cache(void); * * @start: Start address of the region * @size: Size of the region + * @cached: Map memory as cacheable or not * @emerg: Also map the region in the emergency table */ -void mmu_map_region(phys_addr_t start, u64 size, bool emerg); +void mmu_map_region(phys_addr_t start, u64 size, bool cached, bool emerg); /** * mmu_change_region_attr() - change a mapped region attributes diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c index d0a6047b8a65..61e157831231 100644 --- a/drivers/soc/qcom/cmd-db.c +++ b/drivers/soc/qcom/cmd-db.c @@ -248,7 +248,7 @@ static int cmd_db_bind(struct udevice *dev) } /* On SM8550/SM8650 and newer SoCs cmd-db might not be mapped */ - mmu_map_region((phys_addr_t)base, (phys_size_t)size, false); + mmu_map_region((phys_addr_t)base, (phys_size_t)size, false, false); cmd_db_header = base; if (!cmd_db_magic_matches(cmd_db_header)) { diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index e20523e2cec3..1c9ad10ecea7 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -974,7 +974,7 @@ static int qcom_smem_map_toc(struct qcom_smem *smem, struct smem_region *region) return -ENOMEM; if (dcache_status()) - mmu_map_region(region->aux_base, region->size, false); + mmu_map_region(region->aux_base, region->size, false, false); return 0; } @@ -1030,7 +1030,8 @@ int qcom_smem_init(void) int i; if (dcache_status() && smem) - mmu_map_region(smem->regions[0].aux_base, smem->regions[0].size, false); + mmu_map_region(smem->regions[0].aux_base, smem->regions[0].size, + false, false); if (smem) return 0; From c964b943cc1fdcd6536a20708cac8aace0dcb879 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 8 May 2026 12:39:16 +0530 Subject: [PATCH 63/70] dts: talos-evk-u-boot: drop memory override node With SMEM based memory information available, let's drop memory node U-Boot DT override for talos-evk. Signed-off-by: Aswin Murugan --- arch/arm/dts/talos-evk-u-boot.dtsi | 7 ------- 1 file changed, 7 deletions(-) diff --git a/arch/arm/dts/talos-evk-u-boot.dtsi b/arch/arm/dts/talos-evk-u-boot.dtsi index d1ea27206c45..f51d58b26ff5 100644 --- a/arch/arm/dts/talos-evk-u-boot.dtsi +++ b/arch/arm/dts/talos-evk-u-boot.dtsi @@ -4,13 +4,6 @@ */ / { - memory@80000000 { - device_type = "memory"; - reg = <0x0 0x80000000 0x0 0x7a00000>, - <0x0 0x89600000 0x0 0x30100000>, - <0x0 0xc0000000 0x0 0xc0000000>; - }; - reserved-memory { #address-cells = <2>; #size-cells = <2>; From 8005ad78b3c84477bc2fccecc78b916970bb2c7c Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Fri, 8 May 2026 12:12:04 +0530 Subject: [PATCH 64/70] qcom: smem: Use mmu_status() instead of dcache_status() Fix issue where dcache_status() returns true before MMU initialization, causing mmu_map_region() to be called prematurely. Replace with mmu_status() which correctly checks if MMU and page tables are ready. This is semantically correct since we're checking if MMU operations are safe, not specifically if data cache is enabled. Signed-off-by: Aswin Murugan --- drivers/soc/qcom/smem.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 1c9ad10ecea7..7acf5b300eb1 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -973,7 +973,7 @@ static int qcom_smem_map_toc(struct qcom_smem *smem, struct smem_region *region) if (!region->virt_base || !smem->ptable) return -ENOMEM; - if (dcache_status()) + if (mmu_status()) mmu_map_region(region->aux_base, region->size, false, false); return 0; @@ -1029,7 +1029,7 @@ int qcom_smem_init(void) int ret; int i; - if (dcache_status() && smem) + if (mmu_status() && smem) mmu_map_region(smem->regions[0].aux_base, smem->regions[0].size, false, false); From 3eb3a60fca9dd5aa8e9124bdb9d7f4fbe575705d Mon Sep 17 00:00:00 2001 From: Michael Srba Date: Fri, 8 May 2026 02:33:26 +0200 Subject: [PATCH 65/70] mach-snapdragon: boot0.h: split out msm8916_boot0.h Prepare for supporting alternative boot0.h per-SoC by splitting out the existing msm8916-specific code. There is now a selection mechanism to choose a specific boot0.h in the Kconfig. BOOT0_MSM8916_PSCI_WORKAROUND is the only option right now, but more can be added. The toplevel boot0.h additionally enables conditionally performing the include only in u-boot proper, or only in SPL. Signed-off-by: Michael Srba --- arch/arm/mach-snapdragon/Kconfig | 16 +++++ arch/arm/mach-snapdragon/include/mach/boot0.h | 60 +++---------------- .../include/mach/msm8916_boot0.h | 54 +++++++++++++++++ configs/dragonboard410c_defconfig | 1 + 4 files changed, 79 insertions(+), 52 deletions(-) create mode 100644 arch/arm/mach-snapdragon/include/mach/msm8916_boot0.h diff --git a/arch/arm/mach-snapdragon/Kconfig b/arch/arm/mach-snapdragon/Kconfig index 0f03672ee6bc..7f8d62679a5c 100644 --- a/arch/arm/mach-snapdragon/Kconfig +++ b/arch/arm/mach-snapdragon/Kconfig @@ -50,4 +50,20 @@ config QCOM_FIT_MULTIDTB This allows U-Boot to select the appropriate device tree from a FIT image. +choice + prompt "Qualcomm boot0.h workaround" + optional + help + While U-Boot on Qualcomm platforms doesn't generally need compile-time + adjustments based on the target SoC, workarounds in boot0.h can't + practically detect the SoC at runtime. Enable one of these workarounds + if you know you need it. + +config BOOT0_MSM8916_PSCI_WORKAROUND + bool "boot0.h workaround for buggy PSCI on the msm8916 SoC" + help + Select this if you are building U-Boot proper for an msm8916 board that + uses the buggy PSCI implementation. +endchoice + endif diff --git a/arch/arm/mach-snapdragon/include/mach/boot0.h b/arch/arm/mach-snapdragon/include/mach/boot0.h index 953cccad7908..b3c76d6d97dc 100644 --- a/arch/arm/mach-snapdragon/include/mach/boot0.h +++ b/arch/arm/mach-snapdragon/include/mach/boot0.h @@ -1,54 +1,10 @@ /* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Workaround for "PSCI bug" on DragonBoard 410c - * Copyright (C) 2021 Stephan Gerhold - * - * Syscall parameters taken from Qualcomm's LK fork (scm.h): - * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. - * - * The PSCI implementation in the TrustZone/tz firmware on DragonBoard 410c has - * a bug that starts all other CPU cores in 32-bit mode unless the TZ syscall - * that switches from 32-bit to 64-bit mode is executed at least once. - * - * Normally this happens inside Qualcomm's LK bootloader which runs in 32-bit - * mode and uses the TZ syscall to boot a kernel in 64-bit mode. However, if - * U-Boot is installed to the "aboot" partition (replacing LK) the switch to - * 64-bit mode never happens since U-Boot is already running in 64-bit mode. - * - * A workaround for this "PSCI bug" is to execute the TZ syscall when entering - * U-Boot. That way PSCI is made aware of the 64-bit switch and starts all other - * CPU cores in 64-bit mode as well. - */ -#include - -#define ARM_SMCCC_SIP32_FAST_CALL \ - ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, ARM_SMCCC_OWNER_SIP, 0) - - /* - * U-Boot might be started in EL2 or EL3 with custom firmware. - * In that case, we assume that the workaround is not necessary or is - * handled already by the alternative firmware. Using the syscall in EL2 - * would demote U-Boot to EL1; in EL3 it would probably just crash. - */ - mrs x0, CurrentEL - cmp x0, #(1 << 2) /* EL1 */ - bne reset - - /* Prepare TZ syscall parameters */ - mov x0, #ARM_SMCCC_SIP32_FAST_CALL - movk x0, #0x10f /* SCM_SVC_MILESTONE_CMD_ID */ - mov x1, #0x12 /* MAKE_SCM_ARGS(0x2, SMC_PARAM_TYPE_BUFFER_READ) */ - adr x2, el1_system_param - mov x3, el1_system_param_end - el1_system_param - - /* Switch PSCI to 64-bit mode. Resets CPU and returns at el1_elr */ - smc #0 - - /* Something went wrong, perhaps PSCI is already in 64-bit mode? */ +#if defined(CONFIG_SPL_BUILD) b reset - - .align 3 -el1_system_param: - .quad 0, 0, 0, 0, 0, 0, 0, 0, 0 /* el1_x0-x8 */ - .quad reset /* el1_elr */ -el1_system_param_end: +#else +#if defined(CONFIG_BOOT0_MSM8916_PSCI_WORKAROUND) +#include "msm8916_boot0.h" +#else + b reset +#endif +#endif diff --git a/arch/arm/mach-snapdragon/include/mach/msm8916_boot0.h b/arch/arm/mach-snapdragon/include/mach/msm8916_boot0.h new file mode 100644 index 000000000000..953cccad7908 --- /dev/null +++ b/arch/arm/mach-snapdragon/include/mach/msm8916_boot0.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Workaround for "PSCI bug" on DragonBoard 410c + * Copyright (C) 2021 Stephan Gerhold + * + * Syscall parameters taken from Qualcomm's LK fork (scm.h): + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * + * The PSCI implementation in the TrustZone/tz firmware on DragonBoard 410c has + * a bug that starts all other CPU cores in 32-bit mode unless the TZ syscall + * that switches from 32-bit to 64-bit mode is executed at least once. + * + * Normally this happens inside Qualcomm's LK bootloader which runs in 32-bit + * mode and uses the TZ syscall to boot a kernel in 64-bit mode. However, if + * U-Boot is installed to the "aboot" partition (replacing LK) the switch to + * 64-bit mode never happens since U-Boot is already running in 64-bit mode. + * + * A workaround for this "PSCI bug" is to execute the TZ syscall when entering + * U-Boot. That way PSCI is made aware of the 64-bit switch and starts all other + * CPU cores in 64-bit mode as well. + */ +#include + +#define ARM_SMCCC_SIP32_FAST_CALL \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, ARM_SMCCC_OWNER_SIP, 0) + + /* + * U-Boot might be started in EL2 or EL3 with custom firmware. + * In that case, we assume that the workaround is not necessary or is + * handled already by the alternative firmware. Using the syscall in EL2 + * would demote U-Boot to EL1; in EL3 it would probably just crash. + */ + mrs x0, CurrentEL + cmp x0, #(1 << 2) /* EL1 */ + bne reset + + /* Prepare TZ syscall parameters */ + mov x0, #ARM_SMCCC_SIP32_FAST_CALL + movk x0, #0x10f /* SCM_SVC_MILESTONE_CMD_ID */ + mov x1, #0x12 /* MAKE_SCM_ARGS(0x2, SMC_PARAM_TYPE_BUFFER_READ) */ + adr x2, el1_system_param + mov x3, el1_system_param_end - el1_system_param + + /* Switch PSCI to 64-bit mode. Resets CPU and returns at el1_elr */ + smc #0 + + /* Something went wrong, perhaps PSCI is already in 64-bit mode? */ + b reset + + .align 3 +el1_system_param: + .quad 0, 0, 0, 0, 0, 0, 0, 0, 0 /* el1_x0-x8 */ + .quad reset /* el1_elr */ +el1_system_param_end: diff --git a/configs/dragonboard410c_defconfig b/configs/dragonboard410c_defconfig index 51ad265688c4..645f08167010 100644 --- a/configs/dragonboard410c_defconfig +++ b/configs/dragonboard410c_defconfig @@ -11,6 +11,7 @@ CONFIG_ENV_OFFSET=0x0 CONFIG_DEFAULT_DEVICE_TREE="qcom/apq8016-sbc" CONFIG_OF_LIBFDT_OVERLAY=y CONFIG_SYS_LOAD_ADDR=0x80080000 +CONFIG_BOOT0_MSM8916_PSCI_WORKAROUND=y CONFIG_IDENT_STRING="\nQualcomm-DragonBoard 410C" CONFIG_REMAKE_ELF=y CONFIG_BUTTON_CMD=y From bcdc6f78d81555274d91ff6bbce0e66febf1effa Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Mon, 11 May 2026 12:40:52 +0530 Subject: [PATCH 66/70] mach-snapdragon: Add early EL2 hypervisor switch support Add support for exiting Gunyah hypervisor and switching to EL2 during early boot. This is required for platforms that boot U-Boot in a hypervisor guest environment where U-Boot needs to run at EL2 to enable other hypervisors like KVM. The switch is performed via TrustZone SMC call before EL register configuration in start.S. The implementation: - Checks current exception level and only executes at EL1 - Uses TrustZone SMC to exit Gunyah and transition to EL2 - Integrates with existing boot0.h workaround infrastructure A new Kconfig option CONFIG_QCOM_EL2_GUNYAH_EXIT_SUPPORT enables this functionality. Signed-off-by: Aswin Murugan --- arch/arm/mach-snapdragon/Kconfig | 7 ++++ arch/arm/mach-snapdragon/include/mach/boot0.h | 3 ++ .../include/mach/gunyah_exit_boot0.h | 35 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 arch/arm/mach-snapdragon/include/mach/gunyah_exit_boot0.h diff --git a/arch/arm/mach-snapdragon/Kconfig b/arch/arm/mach-snapdragon/Kconfig index 7f8d62679a5c..6e90376007e5 100644 --- a/arch/arm/mach-snapdragon/Kconfig +++ b/arch/arm/mach-snapdragon/Kconfig @@ -64,6 +64,13 @@ config BOOT0_MSM8916_PSCI_WORKAROUND help Select this if you are building U-Boot proper for an msm8916 board that uses the buggy PSCI implementation. + +config QCOM_EL2_GUNYAH_EXIT_SUPPORT + bool "Enable early EL2 switch by exiting Gunyah" + help + Exit Gunyah hypervisor and switch to EL2 during early boot. This must + happen before EL register configuration in start.S so that U-Boot can + run properly at EL2. endchoice endif diff --git a/arch/arm/mach-snapdragon/include/mach/boot0.h b/arch/arm/mach-snapdragon/include/mach/boot0.h index b3c76d6d97dc..032a9fbdb12b 100644 --- a/arch/arm/mach-snapdragon/include/mach/boot0.h +++ b/arch/arm/mach-snapdragon/include/mach/boot0.h @@ -1,9 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0+ */ + #if defined(CONFIG_SPL_BUILD) b reset #else #if defined(CONFIG_BOOT0_MSM8916_PSCI_WORKAROUND) #include "msm8916_boot0.h" +#elif defined(CONFIG_QCOM_EL2_GUNYAH_EXIT_SUPPORT) +#include "gunyah_exit_boot0.h" #else b reset #endif diff --git a/arch/arm/mach-snapdragon/include/mach/gunyah_exit_boot0.h b/arch/arm/mach-snapdragon/include/mach/gunyah_exit_boot0.h new file mode 100644 index 000000000000..5c5bd3b66919 --- /dev/null +++ b/arch/arm/mach-snapdragon/include/mach/gunyah_exit_boot0.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Early Gunyah Hypervisor Exit + * + * Exit Gunyah hypervisor and switch to EL2 during early boot. This must + * happen before EL register configuration in start.S so that U-Boot can + * run properly at EL2. + */ + +#include +#include + +/* TrustZone SMC IDs for hypervisor configuration */ +#define TZ_EL2_SWITCH_SMC_ID 0x02000121 +#define TZ_EL2_SWITCH_PARAM_ID 0x00000023 +#define TZ_EL2_SWITCH_PARAM2_EXIT_GUNYAH 0x1 + + /* Only perform hypervisor switch if we're at EL1 */ + switch_el x9, 3f, 2f, 1f + + /* Save FDT address before we modify x0 */ +1: mov x9, x0 + + /* Switch to EL2 (exit Gunyah) */ + ldr w0, =TZ_EL2_SWITCH_SMC_ID + ldr w1, =TZ_EL2_SWITCH_PARAM_ID + mov w2, wzr + mov w3, wzr + ldr w4, =TZ_EL2_SWITCH_PARAM2_EXIT_GUNYAH + smc #0 + + /* Restore FDT address */ + mov x0, x9 +2: +3: b reset From d934690e072281f0347905e82146d8854b19c39e Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Tue, 12 May 2026 00:09:14 +0530 Subject: [PATCH 67/70] configs: qcom: add EL2 Gunyah exit and boot0 hook support Enable Gunyah EL2 exit handling and ARM SoC boot0 hook in Qualcomm defconfigs (qcm6490, qcom_lemans, qcom_qcs615) to support early EL2 transitions and platform boot flow requirements. Signed-off-by: Aswin Murugan --- configs/qcm6490_defconfig | 3 +++ configs/qcom_lemans_defconfig | 3 +++ configs/qcom_qcs615_defconfig | 3 +++ 3 files changed, 9 insertions(+) diff --git a/configs/qcm6490_defconfig b/configs/qcm6490_defconfig index c06b0f9548d7..cafd9233c19b 100644 --- a/configs/qcm6490_defconfig +++ b/configs/qcm6490_defconfig @@ -21,3 +21,6 @@ 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" + +CONFIG_QCOM_EL2_GUNYAH_EXIT_SUPPORT=y +CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK=y diff --git a/configs/qcom_lemans_defconfig b/configs/qcom_lemans_defconfig index c88e1c9242a3..c604c9989474 100644 --- a/configs/qcom_lemans_defconfig +++ b/configs/qcom_lemans_defconfig @@ -16,3 +16,6 @@ CONFIG_ENV_SCSI_PART_USE_TYPE_GUID=y 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 + +CONFIG_QCOM_EL2_GUNYAH_EXIT_SUPPORT=y +CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK=y diff --git a/configs/qcom_qcs615_defconfig b/configs/qcom_qcs615_defconfig index 718b4276dfe5..c90fa8bafb19 100644 --- a/configs/qcom_qcs615_defconfig +++ b/configs/qcom_qcs615_defconfig @@ -35,3 +35,6 @@ CONFIG_ENV_MMC_USE_SW_PARTITION=y CONFIG_ENV_MMC_SW_PARTITION="logfs" CONFIG_ENV_OFFSET=0x0 CONFIG_ENV_SIZE=0x4000 + +CONFIG_QCOM_EL2_GUNYAH_EXIT_SUPPORT=y +CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK=y From b66c517f08179130736ada0c707ba7da9d52b620 Mon Sep 17 00:00:00 2001 From: Aswin Murugan Date: Thu, 14 May 2026 22:36:34 +0530 Subject: [PATCH 68/70] tools: efivar: strip auth descriptor for authenticated variables efivar.py currently stores authenticated variables including the EFI_VARIABLE_AUTHENTICATION_2 descriptor (timestamp + WIN_CERTIFICATE) along with the payload. When variables are set via U-Boot, SetVariable() validates and strips this authentication descriptor before persisting the variable data, resulting in only the payload being stored and returned by GetVariable(). This mismatch causes efivar.py-generated stores to differ from U-Boot runtime behavior and leads to incorrect GetVariable() results. Update efivar.py to strip the authentication descriptor and store only the payload for authenticated variables, ensuring consistency with U-Boot behavior and compliance with UEFI expectations. Signed-off-by: Aswin Murugan --- tools/efivar.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tools/efivar.py b/tools/efivar.py index 67729fa8505c..d248cd868ba5 100755 --- a/tools/efivar.py +++ b/tools/efivar.py @@ -79,6 +79,50 @@ def __init__(self, size, attrs, time, guid, name, data): def calc_crc32(buf): return zlib.crc32(buf) & 0xffffffff +def strip_auth_descriptor(data, attrs): + """ + Strip the EFI auth descriptor from authenticated variable data. + + This is used during efivar.py-based pre-seeding of ubootefi.var to + match U-Boot SetVariable() behavior, where the authentication header + is consumed during validation and only the payload is stored. + + For variables with EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS: + - Input format: [EFI_VARIABLE_AUTHENTICATION_2 | payload] + - Stored format: [payload only] + + Auth header size calculation (aligned with U-Boot efi_variable_authenticate()): + auth_size = sizeof(EFI_TIME) + WIN_CERTIFICATE.dwLength + + Only the payload portion is retained after stripping the header. + """ + if not (attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS): + return data + if not data: + return data + + efi = EfiStruct() + if len(data) < efi.var_time_size: + return data + + offset = efi.var_time_size + if len(data) < offset + efi.var_win_cert_size: + return data + + try: + cert_hdr = struct.unpack_from(efi.var_win_cert_fmt, data, offset) + dwLength = cert_hdr[0] + except struct.error: + return data + + auth_size = efi.var_time_size + dwLength + if auth_size <= 0 or auth_size > len(data): + return data + + if len(data) <= auth_size: + return b'' + return data[auth_size:] + class EfiVariableStore: def __init__(self, infile): self.infile = infile @@ -172,8 +216,12 @@ def set_var(self, guid, name, data, size, attrs): break offs = loffs + if data and (attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS): + data = strip_auth_descriptor(data, attrs) + size = len(data) if data else 0 + tsec = int(time.time()) if attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0 - nd = name.encode('utf_16_le') + b"\x00\x00" + data + nd = name.encode('utf_16_le') + b"\x00\x00" + (data if data else b'') # U-Boot variable format requires the name + data blob to be 8-byte aligned pad = ((len(nd) + 7) & ~7) - len(nd) nd += bytes([0] * pad) From 76cd6016e6657fae9cce9de29da0604ec3214cb1 Mon Sep 17 00:00:00 2001 From: Ricardo Salveti Date: Thu, 14 May 2026 19:33:33 +0000 Subject: [PATCH 69/70] board/qualcomm: use bootflow for default boot The Qualcomm default environment currently boots only with 'bootefi bootmgr'. This bypasses the standard boot flow used to discover extlinux and script boot entries, which are commonly used for FIT-based Linux boots. Switch do_boot to 'bootflow scan -lb' so bootstd handles boot method selection. This keeps EFI support available through the EFI boot methods while also enabling extlinux/boot.scr based bootflows by default. Upstream-Status: Submitted [https://lists.denx.de/pipermail/u-boot/2026-May/618641.html] Signed-off-by: Ricardo Salveti --- 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 429d71d57f3c..e61c99870e6a 100644 --- a/board/qualcomm/default.env +++ b/board/qualcomm/default.env @@ -3,7 +3,7 @@ stdout=serial,vidconsole stderr=serial,vidconsole 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 +do_boot=bootflow scan -lb bootmenu_0=Boot first available device=run do_boot bootmenu_1=Enable fastboot mode=run fastboot bootmenu_2=Reset device=reset From fed81a1257ea64b29de76cc700cfed41cfe2eb8d Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Fri, 22 May 2026 18:12:04 +0530 Subject: [PATCH 70/70] arm: dts: talos: Disable SD card controller The Talos EVK does not have SD card support. So, disable the sdhc_2 node in the U-Boot device tree to prevent unnecessary initialization attempts and eliminate boot warnings. Signed-off-by: Balaji Selvanathan --- arch/arm/dts/talos-evk-u-boot.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/dts/talos-evk-u-boot.dtsi b/arch/arm/dts/talos-evk-u-boot.dtsi index f51d58b26ff5..068ececd7100 100644 --- a/arch/arm/dts/talos-evk-u-boot.dtsi +++ b/arch/arm/dts/talos-evk-u-boot.dtsi @@ -60,3 +60,8 @@ }; }; }; + +/* Disable SD card controller */ +&sdhc_2 { + status = "disabled"; +};