Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions arch/arm/dts/qcs615-ride-u-boot.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally address values are expected to be spelled out in full. i.e. 0x00000800

Copy link
Copy Markdown
Author

@aswinm94 aswinm94 Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@varada-qcom , It was referred from other soc dts in kernel, they didn't spelled out fully so I did the same to maintain the uniformity
https://source.denx.de/u-boot/u-boot/-/blob/master/dts/upstream/src/arm64/qcom/lemans-pmics.dtsi#L154
Do we need to change it?

#address-cells = <1>;
#size-cells = <1>;
ranges = <0x00 0x800 0x100>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here


/* Reboot reason cell at PON_SOFT_RB_SPARE (0x88F) */
reboot_reason: reboot-reason@8f {
reg = <0x8f 0x1>;
bits = <1 7>;
};
};
};
58 changes: 47 additions & 11 deletions arch/arm/mach-snapdragon/board.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,30 @@
#define LOG_CATEGORY LOGC_BOARD
#define pr_fmt(fmt) "QCOM: " fmt

#include <command.h>
#include <env.h>
#include <fdt_support.h>
#include <init.h>
#include <lmb.h>
#include <malloc.h>
#include <sort.h>
#include <time.h>
#include <usb.h>
#include <asm/armv8/mmu.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/psci.h>
#include <asm/system.h>
#include <dm/device.h>
#include <dm/pinctrl.h>
#include <dm/uclass-internal.h>
#include <dm/read.h>
#include <power/regulator.h>
#include <env.h>
#include <fdt_support.h>
#include <init.h>
#include <dm/uclass-internal.h>
#include <linux/arm-smccc.h>
#include <linux/bug.h>
#include <linux/psci.h>
#include <linux/sizes.h>
#include <lmb.h>
#include <malloc.h>
#include <fdt_support.h>
#include <usb.h>
#include <sort.h>
#include <time.h>
#include <power/regulator.h>
#include <reboot-mode/reboot-mode.h>

#include "qcom-priv.h"

Expand Down Expand Up @@ -506,6 +507,38 @@ void qcom_show_boot_source(void)
env_set("boot_source", name);
}

/**
* qcom_handle_reboot_mode() - Process reboot-mode detection and handle fastboot entry
*
* This function detects the reboot reason from PMIC registers and automatically
* enters fastboot mode if the reboot reason was "bootloader".
*/
static void qcom_handle_reboot_mode(void)
{
struct udevice *reboot_dev;
const char *reboot_mode;
int ret;

if (!IS_ENABLED(CONFIG_DM_REBOOT_MODE))
return;

ret = uclass_first_device_err(UCLASS_REBOOT_MODE, &reboot_dev);
if (ret)
return;

ret = dm_reboot_mode_update(reboot_dev);
if (ret)
return;

reboot_mode = env_get("reboot-mode");
if (reboot_mode && !strcmp(reboot_mode, "bootloader")) {
log_info("Entering fastboot mode due to reboot reason...\n");
ret = run_command("run fastboot", 0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 'fastboot' a variable? Why run within run_command()

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it is a variable

if (ret)
log_warning("Failed to enter fastboot mode: %d\n", ret);
}
}

void __weak qcom_late_init(void)
{
}
Expand Down Expand Up @@ -570,6 +603,9 @@ int board_late_init(void)
qcom_late_init();

qcom_show_boot_source();

qcom_handle_reboot_mode();

/* Configure the dfu_string for capsule updates */
qcom_configure_capsule_updates();

Expand Down
14 changes: 14 additions & 0 deletions arch/sandbox/dts/test.dts
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,13 @@
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";
};
Expand All @@ -978,6 +985,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 {
Expand Down
3 changes: 3 additions & 0 deletions configs/qcom_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ CONFIG_PINCONF=y
CONFIG_PINCTRL_QCOM_GENERIC=y
CONFIG_DM_PMIC=y
CONFIG_PMIC_QCOM=y
CONFIG_DM_REBOOT_MODE=y
CONFIG_DM_REGULATOR=y
CONFIG_DM_REGULATOR_FIXED=y
CONFIG_DM_REGULATOR_QCOM_RPMH=y
Expand All @@ -127,6 +128,8 @@ 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_NVMEM=y
CONFIG_SPMI_MSM=y
CONFIG_SYSINFO=y
CONFIG_SYSINFO_SMBIOS=y
Expand Down
8 changes: 8 additions & 0 deletions drivers/misc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions drivers/misc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
166 changes: 144 additions & 22 deletions drivers/misc/nvmem.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,55 +12,163 @@
#include <dm/ofnode.h>
#include <dm/read.h>
#include <dm/uclass.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <asm/byteorder.h>

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 %d, 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 %d, 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, &current, sizeof(current));
if (ret < 0)
return ret;

current = le32_to_cpu(*((__le32 *)&current));
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 = &current;
} 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;
}

/**
Expand Down Expand Up @@ -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;
}

Expand Down
Loading