mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 01:49:46 +00:00
nvmem: raspberrypi: Add nvmem driver for accessing OTP data
This supports reading and writing OTP using the firmware mailbox interface. It needs supporting firmware to run. Signed-off-by: Dom Cobley <popcornmix@gmail.com>
This commit is contained in:
@@ -63,6 +63,18 @@ config NVMEM_APPLE_SPMI
|
|||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called apple-nvmem-spmi.
|
will be called apple-nvmem-spmi.
|
||||||
|
|
||||||
|
config NVMEM_RASPBERRYPI_OTP
|
||||||
|
tristate "Raspberry Pi OTP support"
|
||||||
|
depends on (ARCH_BCM && RASPBERRYPI_FIRMWARE) || COMPILE_TEST
|
||||||
|
default ARCH_BCM && RASPBERRYPI_FIRMWARE
|
||||||
|
help
|
||||||
|
Say y here to enable support for accessing OTP on Raspberry Pi boards.
|
||||||
|
These are used to store non-volatile information such as serial number,
|
||||||
|
board revision and customer stored data.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module will
|
||||||
|
be called nvmem-raspberrypi-otp.
|
||||||
|
|
||||||
config NVMEM_BCM_OCOTP
|
config NVMEM_BCM_OCOTP
|
||||||
tristate "Broadcom On-Chip OTP Controller support"
|
tristate "Broadcom On-Chip OTP Controller support"
|
||||||
depends on ARCH_BCM_IPROC || COMPILE_TEST
|
depends on ARCH_BCM_IPROC || COMPILE_TEST
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o
|
|||||||
nvmem-apple-efuses-y := apple-efuses.o
|
nvmem-apple-efuses-y := apple-efuses.o
|
||||||
obj-$(CONFIG_NVMEM_APPLE_SPMI) += apple_nvmem_spmi.o
|
obj-$(CONFIG_NVMEM_APPLE_SPMI) += apple_nvmem_spmi.o
|
||||||
apple_nvmem_spmi-y := apple-spmi-nvmem.o
|
apple_nvmem_spmi-y := apple-spmi-nvmem.o
|
||||||
|
obj-$(CONFIG_NVMEM_RASPBERRYPI_OTP) += nvmem-raspberrypi-otp.o
|
||||||
|
nvmem-raspberrypi-otp-y := raspberrypi-otp.o
|
||||||
obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o
|
obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o
|
||||||
nvmem-bcm-ocotp-y := bcm-ocotp.o
|
nvmem-bcm-ocotp-y := bcm-ocotp.o
|
||||||
obj-$(CONFIG_NVMEM_BRCM_NVRAM) += nvmem_brcm_nvram.o
|
obj-$(CONFIG_NVMEM_BRCM_NVRAM) += nvmem_brcm_nvram.o
|
||||||
|
|||||||
135
drivers/nvmem/raspberrypi-otp.c
Normal file
135
drivers/nvmem/raspberrypi-otp.c
Normal file
@@ -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 <popcornmix@gmail.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
Reference in New Issue
Block a user