[PATCH v2 2/4] power: reset: add PSCRR NVMEM recorder

Faruque Ansari <[email protected]>
Newsgroups org.kernel.vger.linux-watchdog,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm
Message-ID <[email protected]>
Record the power state change reason into a small NVMEM cell (an RTC
scratch register or EEPROM) and read it back on the next boot, so the
cause survives a power cycle. The cell is a plain nvmem consumer
reference and the reason value is stored directly.

Co-developed-by: Oleksij Rempel <[email protected]>
Signed-off-by: Oleksij Rempel <[email protected]>
Signed-off-by: Faruque Ansari <[email protected]>
---
 drivers/power/reset/pscrr/Kconfig       |  14 +++
 drivers/power/reset/pscrr/Makefile      |   1 +
 drivers/power/reset/pscrr/pscrr-nvmem.c | 154 ++++++++++++++++++++++++++++++++
 3 files changed, 169 insertions(+)

diff --git a/drivers/power/reset/pscrr/Kconfig b/drivers/power/reset/pscrr/Kconfig
index 72de82731b53..bfcf04b0c8f5 100644
--- a/drivers/power/reset/pscrr/Kconfig
+++ b/drivers/power/reset/pscrr/Kconfig
@@ -31,3 +31,17 @@ menuconfig PSCRR
 	  unless hardware provides the reset cause.
 
 	  If unsure, say N.
+
+if PSCRR
+
+config PSCRR_NVMEM
+	tristate "PSCRR NVMEM recorder provider"
+	depends on NVMEM
+	help
+	  PSCRR recorder that stores the power state change reason in a
+	  small NVMEM cell (such as an RTC scratch register) and reads it
+	  back on the next boot, so the cause survives a power cycle.
+
+	  If unsure, say N.
+
+endif # PSCRR
diff --git a/drivers/power/reset/pscrr/Makefile b/drivers/power/reset/pscrr/Makefile
index e5530a858971..95c80c80c7da 100644
--- a/drivers/power/reset/pscrr/Makefile
+++ b/drivers/power/reset/pscrr/Makefile
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_PSCRR) += pscrr.o
+obj-$(CONFIG_PSCRR_NVMEM) += pscrr-nvmem.o
diff --git a/drivers/power/reset/pscrr/pscrr-nvmem.c b/drivers/power/reset/pscrr/pscrr-nvmem.c
new file mode 100644
index 000000000000..1c3145c0d058
--- /dev/null
+++ b/drivers/power/reset/pscrr/pscrr-nvmem.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * pscrr-nvmem.c - NVMEM recorder provider for PSCRR
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * Copyright (C) 2025 Pengutronix, Oleksij Rempel <[email protected]>
+ */
+
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pscrr.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+
+struct pscrr_nvmem {
+	struct nvmem_cell *cell;
+	enum psc_reason reason;
+};
+
+static int pscrr_nvmem_cell_read(struct pscrr_nvmem *priv, u8 *out)
+{
+	size_t len;
+	void *buf;
+
+	buf = nvmem_cell_read(priv->cell, &len);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+
+	*out = len ? *(u8 *)buf : 0;
+	kfree(buf);
+
+	return 0;
+}
+
+static int pscrr_nvmem_cell_write(struct pscrr_nvmem *priv, u8 val)
+{
+	int ret;
+
+	/* nvmem_cell_write() returns the byte count on success, not 0. */
+	ret = nvmem_cell_write(priv->cell, &val, sizeof(val));
+
+	return ret < 0 ? ret : 0;
+}
+
+static int pscrr_nvmem_read_reasons(struct pscrr_provider *p,
+				    unsigned long *reasons)
+{
+	struct pscrr_nvmem *priv = p->priv;
+
+	/*
+	 * Report the reason latched at probe (or overwritten by a later
+	 * record); the cell itself was cleared at probe, so it is not read
+	 * live here.
+	 */
+	set_bit(READ_ONCE(priv->reason), reasons);
+
+	return 0;
+}
+
+static int pscrr_nvmem_write_reason(struct pscrr_provider *p,
+				    enum psc_reason reason)
+{
+	struct pscrr_nvmem *priv = p->priv;
+	int ret;
+
+	if (reason >= PSCR_REASON_COUNT)
+		return -EINVAL;
+
+	ret = pscrr_nvmem_cell_write(priv, reason);
+	if (ret)
+		return ret;
+
+	WRITE_ONCE(priv->reason, reason);
+
+	return 0;
+}
+
+static const struct pscrr_provider_ops pscrr_nvmem_ops = {
+	.read_reasons = pscrr_nvmem_read_reasons,
+	.write_reason = pscrr_nvmem_write_reason,
+};
+
+static int pscrr_nvmem_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct pscrr_nvmem *priv;
+	size_t bytes, bits;
+	u8 val;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->cell = devm_nvmem_cell_get(dev, "pscr");
+	if (IS_ERR(priv->cell))
+		return dev_err_probe(dev, PTR_ERR(priv->cell),
+				     "failed to get the pscr nvmem cell\n");
+
+	ret = nvmem_cell_get_size(priv->cell, &bytes, &bits);
+	if (ret)
+		return ret;
+
+	/* The reason is a single byte; a bit cell still occupies one. */
+	if ((bytes ?: DIV_ROUND_UP(bits, 8)) != sizeof(val))
+		return dev_err_probe(dev, -EINVAL,
+				     "unsupported pscr nvmem cell size\n");
+
+	/*
+	 * The cell survives resets, so at boot it still holds whatever the
+	 * previous session recorded before it went down. Latch that as this
+	 * boot's reason, then clear the cell back to PSCR_UNKNOWN: if this
+	 * session is later cut short by an abrupt reset that never runs the
+	 * recorder (watchdog power-cycle, sudden power loss), the next boot
+	 * reads "unknown" instead of a stale reason left over from an earlier
+	 * cycle.
+	 */
+	ret = pscrr_nvmem_cell_read(priv, &val);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to read the pscr nvmem cell\n");
+
+	priv->reason = val < PSCR_REASON_COUNT ? val : PSCR_UNKNOWN;
+
+	ret = pscrr_nvmem_cell_write(priv, PSCR_UNKNOWN);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to clear the pscr nvmem cell\n");
+
+	return PTR_ERR_OR_ZERO(devm_pscrr_provider_register(dev, "nvmem",
+							    &pscrr_nvmem_ops, NULL,
+							    priv));
+}
+
+static const struct of_device_id pscrr_nvmem_of_match[] = {
+	{ .compatible = "pscrr-nvmem" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pscrr_nvmem_of_match);
+
+static struct platform_driver pscrr_nvmem_driver = {
+	.probe	= pscrr_nvmem_probe,
+	.driver	= {
+		.name		= "pscrr-nvmem",
+		.of_match_table	= pscrr_nvmem_of_match,
+	},
+};
+module_platform_driver(pscrr_nvmem_driver);
+
+MODULE_AUTHOR("Oleksij Rempel <[email protected]>");
+MODULE_AUTHOR("Faruque Ansari <[email protected]>");
+MODULE_DESCRIPTION("NVMEM recorder provider for PSCRR");
+MODULE_LICENSE("GPL");

-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.