2024-01-19 13:25:50

by Oleksij Rempel

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

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 <[email protected]>
---
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 <[email protected]>
+/*
+ * 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 <[email protected]>");
+MODULE_DESCRIPTION("NVMEM Driver for Power State Change Reason Tracking");
+MODULE_LICENSE("GPL v2");
--
2.39.2