1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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");
|