aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/nvmem/raspberrypi-otp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/nvmem/raspberrypi-otp.c')
-rw-r--r--drivers/nvmem/raspberrypi-otp.c135
1 files changed, 135 insertions, 0 deletions
diff --git a/drivers/nvmem/raspberrypi-otp.c b/drivers/nvmem/raspberrypi-otp.c
new file mode 100644
index 000000000000..a7c735e9e964
--- /dev/null
+++ b/drivers/nvmem/raspberrypi-otp.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/**
+ * raspberrypi-otp.c
+ *
+ * nvmem driver using firmware mailbox to access otp
+ *
+ * Copyright (c) 2024, Raspberry Pi Ltd.
+ */
+
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+struct rpi_otp_priv {
+ struct rpi_firmware *fw;
+ u32 block;
+};
+
+#define MAX_ROWS 192
+
+#define RPI_FIRMWARE_GET_USER_OTP 0x00030024
+#define RPI_FIRMWARE_SET_USER_OTP 0x00038024
+
+static int rpi_otp_read(void *context, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct rpi_otp_priv *priv = context;
+ int words = bytes / sizeof(u32);
+ int index = offset / sizeof(u32);
+ u32 data[3 + MAX_ROWS] = {priv->block, index, words};
+ int err = 0;
+
+ if (words > MAX_ROWS)
+ return -EINVAL;
+
+ err = rpi_firmware_property(priv->fw, RPI_FIRMWARE_GET_USER_OTP,
+ &data, sizeof(data));
+ if (err == 0)
+ memcpy(val, data + 3, bytes);
+ else
+ memset(val, 0xee, bytes);
+ return err;
+}
+
+static int rpi_otp_write(void *context, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct rpi_otp_priv *priv = context;
+ int words = bytes / sizeof(u32);
+ int index = offset / sizeof(u32);
+ u32 data[3 + MAX_ROWS] = {priv->block, index, words};
+
+ if (bytes > MAX_ROWS * sizeof(u32))
+ return -EINVAL;
+
+ memcpy(data + 3, val, bytes);
+ return rpi_firmware_property(priv->fw, RPI_FIRMWARE_SET_USER_OTP,
+ &data, sizeof(data));
+}
+
+static int rpi_otp_probe(struct platform_device *pdev)
+{
+ struct rpi_otp_priv *priv;
+ struct nvmem_config config = {
+ .dev = &pdev->dev,
+ .reg_read = rpi_otp_read,
+ .reg_write = rpi_otp_write,
+ .stride = sizeof(u32),
+ .word_size = sizeof(u32),
+ .type = NVMEM_TYPE_OTP,
+ .root_only = true,
+ };
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *fw_node;
+ struct rpi_firmware *fw;
+ u32 reg[2];
+ const char *pname;
+
+ if (of_property_read_u32_array(np, "reg", reg, ARRAY_SIZE(reg))) {
+ dev_err(dev, "Failed to parse \"reg\" property\n");
+ return -EINVAL;
+ }
+
+ pname = of_get_property(np, "name", NULL);
+ if (!pname) {
+ dev_err(dev, "Failed to parse \"name\" property\n");
+ return -ENOENT;
+ }
+
+ config.name = pname;
+ config.size = reg[1] * sizeof(u32);
+ config.read_only = !of_property_read_bool(np, "rw");
+
+ fw_node = of_parse_phandle(np, "firmware", 0);
+ if (!fw_node) {
+ dev_err(dev, "Missing firmware node\n");
+ return -ENOENT;
+ }
+
+ fw = rpi_firmware_get(fw_node);
+ if (!fw)
+ return -EPROBE_DEFER;
+
+ priv = devm_kzalloc(config.dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->fw = fw;
+ priv->block = reg[0];
+ config.priv = priv;
+
+ return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config));
+}
+
+static const struct of_device_id rpi_otp_of_match[] = {
+ { .compatible = "raspberrypi,rpi-otp", },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, rpi_otp_of_match);
+
+static struct platform_driver rpi_otp_driver = {
+ .driver = {
+ .name = "rpi_otp",
+ .of_match_table = rpi_otp_of_match,
+ },
+ .probe = rpi_otp_probe,
+};
+
+module_platform_driver(rpi_otp_driver);
+
+MODULE_AUTHOR("Dom Cobley <[email protected]>");
+MODULE_LICENSE("GPL");