diff mbox series

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

Message ID 20240119132521.3609945-6-o.rempel@pengutronix.de (mailing list archive)
State RFC, archived
Headers show
Series Introduction of PSCR Framework and Related Components | expand

Commit Message

Oleksij Rempel Jan. 19, 2024, 1:25 p.m. UTC
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@pengutronix.de>
---
 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 mbox series

Patch

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@pengutronix.de>
+/*
+ * 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@pengutronix.de>");
+MODULE_DESCRIPTION("NVMEM Driver for Power State Change Reason Tracking");
+MODULE_LICENSE("GPL v2");