[RFC PATCH v1 5/7] power: reset: add PSCR NVMEM Driver for Storing Power State Change Reasons

From: Oleksij Rempel
Date: Fri Jan 19 2024 - 08:25:50 EST


This driver utilizes the Power State Change Reasons (PSCR) framework to
store specific power state change information, such as shutdown or
reboot reasons, into a designated non-volatile memory (NVMEM) cell.

Signed-off-by: Oleksij Rempel <o.rempel@xxxxxxxxxxxxxx>
---
drivers/power/reset/Kconfig | 11 ++++
drivers/power/reset/Makefile | 1 +
drivers/power/reset/pscr-nvmem.c | 100 +++++++++++++++++++++++++++++++
3 files changed, 112 insertions(+)
create mode 100644 drivers/power/reset/pscr-nvmem.c

diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index da76e84302b9..a110dff599af 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -324,3 +324,14 @@ menuconfig PSCR
timeouts.

If unsure, say N.
+
+if PSCR
+
+config PSCR_NVMEM
+ tristate "Generic NVMEM-based Power State Change Reason Tracking"
+ depends on OF
+ help
+ Enabling this option adds support for storing power state change
+ reasons in a NVMEM cell.
+
+endif
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index d9d744302c68..e7985d81ac8a 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o
obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o
obj-$(CONFIG_POWER_RESET_RMOBILE) += rmobile-reset.o
obj-$(CONFIG_PSCR) += pscr.o
+obj-$(CONFIG_PSCR_NVMEM) += pscr-nvmem.o
obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o
diff --git a/drivers/power/reset/pscr-nvmem.c b/drivers/power/reset/pscr-nvmem.c
new file mode 100644
index 000000000000..74048341b7d7
--- /dev/null
+++ b/drivers/power/reset/pscr-nvmem.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) Vaisala Oyj. All rights reserved.
+// Copyright (c) 2024 Pengutronix, Oleksij Rempel <kernel@xxxxxxxxxxxxxx>
+/*
+ * Based on drivers/power/reset/nvmem-reboot-mode.c
+ * Copyright (c) Vaisala Oyj. All rights reserved.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pscr.h>
+
+struct pscr_nvmem {
+ struct pscr_driver pscr_drv;
+ struct nvmem_cell *cell;
+ size_t max_magic_bytes;
+};
+
+static int pscr_nvmem_write(struct pscr_driver *pscr_drv, u32 magic)
+{
+ struct pscr_nvmem *priv = container_of(pscr_drv, struct pscr_nvmem,
+ pscr_drv);
+ size_t size = min(priv->max_magic_bytes, sizeof(magic));
+ int ret;
+
+ ret = nvmem_cell_write(priv->cell, &magic, size);
+ if (ret < 0)
+ dev_err(pscr_drv->dev, "update reason bits failed: %pe\n",
+ ERR_PTR(ret));
+
+ return ret;
+}
+
+static int pscr_nvmem_probe(struct platform_device *pdev)
+{
+ const char *pscr = "pscr";
+ struct pscr_nvmem *priv;
+ size_t bytes, bits, magic_bits;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pscr_drv.dev = &pdev->dev;
+ priv->pscr_drv.write = pscr_nvmem_write;
+
+ priv->cell = devm_nvmem_cell_get(&pdev->dev, pscr);
+ if (IS_ERR(priv->cell))
+ return dev_err_probe(&pdev->dev, PTR_ERR(priv->cell),
+ "failed to get the nvmem %s cell\n", pscr);
+
+ ret = nvmem_cell_get_size(priv->cell, &bytes, &bits);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "failed to get the nvmem %s size\n",
+ pscr);
+
+ if (!bytes || bytes > sizeof(u32) || bits > 32)
+ return dev_err_probe(&pdev->dev, -EINVAL, "invalid nvmem %s size. bytes: %zu, bits: %zu\n",
+ pscr, bytes, bits);
+
+ ret = devm_pscr_register(&pdev->dev, &priv->pscr_drv);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to register pscr driver\n");
+
+ magic_bits = fls(priv->pscr_drv.max_magic);
+ priv->max_magic_bytes = DIV_ROUND_UP(magic_bits, 8);
+
+ if (!bits)
+ bits = bytes * 8;
+
+ if (magic_bits > bits)
+ return dev_err_probe(&pdev->dev, -EINVAL, "provided magic can't fit into nvmem %s. bytes: %zu, bits: %zu, magic_bits: %zu\n",
+ pscr, bytes, bits, magic_bits);
+
+ return ret;
+}
+
+static const struct of_device_id pscr_nvmem_of_match[] = {
+ { .compatible = "pscr-nvmem" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, pscr_nvmem_of_match);
+
+static struct platform_driver pscr_nvmem_driver = {
+ .probe = pscr_nvmem_probe,
+ .driver = {
+ .name = "pscr-nvmem",
+ .of_match_table = pscr_nvmem_of_match,
+ },
+};
+module_platform_driver(pscr_nvmem_driver);
+
+MODULE_AUTHOR("Oleksij Rempel <o.rempel@xxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("NVMEM Driver for Power State Change Reason Tracking");
+MODULE_LICENSE("GPL v2");
--
2.39.2