Commit | Line | Data |
---|---|---|
67ff708b PF |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * i.MX8 OCOTP fusebox driver | |
4 | * | |
5 | * Copyright 2019 NXP | |
6 | * | |
7 | * Peng Fan <peng.fan@nxp.com> | |
8 | */ | |
9 | ||
885ce72a | 10 | #include <linux/arm-smccc.h> |
67ff708b PF |
11 | #include <linux/firmware/imx/sci.h> |
12 | #include <linux/module.h> | |
13 | #include <linux/nvmem-provider.h> | |
14 | #include <linux/of_device.h> | |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/slab.h> | |
17 | ||
40bb95db | 18 | #define IMX_SIP_OTP_WRITE 0xc200000B |
885ce72a | 19 | |
67ff708b PF |
20 | enum ocotp_devtype { |
21 | IMX8QXP, | |
a3c59cf2 | 22 | IMX8QM, |
67ff708b PF |
23 | }; |
24 | ||
f8017bfc PF |
25 | #define ECC_REGION BIT(0) |
26 | #define HOLE_REGION BIT(1) | |
27 | ||
28 | struct ocotp_region { | |
29 | u32 start; | |
30 | u32 end; | |
31 | u32 flag; | |
32 | }; | |
33 | ||
67ff708b PF |
34 | struct ocotp_devtype_data { |
35 | int devtype; | |
36 | int nregs; | |
f8017bfc PF |
37 | u32 num_region; |
38 | struct ocotp_region region[]; | |
67ff708b PF |
39 | }; |
40 | ||
41 | struct ocotp_priv { | |
42 | struct device *dev; | |
43 | const struct ocotp_devtype_data *data; | |
44 | struct imx_sc_ipc *nvmem_ipc; | |
45 | }; | |
46 | ||
47 | struct imx_sc_msg_misc_fuse_read { | |
48 | struct imx_sc_rpc_msg hdr; | |
49 | u32 word; | |
50 | } __packed; | |
51 | ||
885ce72a PF |
52 | static DEFINE_MUTEX(scu_ocotp_mutex); |
53 | ||
67ff708b PF |
54 | static struct ocotp_devtype_data imx8qxp_data = { |
55 | .devtype = IMX8QXP, | |
56 | .nregs = 800, | |
f8017bfc PF |
57 | .num_region = 3, |
58 | .region = { | |
59 | {0x10, 0x10f, ECC_REGION}, | |
60 | {0x110, 0x21F, HOLE_REGION}, | |
61 | {0x220, 0x31F, ECC_REGION}, | |
62 | }, | |
67ff708b PF |
63 | }; |
64 | ||
a3c59cf2 FD |
65 | static struct ocotp_devtype_data imx8qm_data = { |
66 | .devtype = IMX8QM, | |
67 | .nregs = 800, | |
f8017bfc PF |
68 | .num_region = 2, |
69 | .region = { | |
70 | {0x10, 0x10f, ECC_REGION}, | |
71 | {0x1a0, 0x1ff, ECC_REGION}, | |
72 | }, | |
a3c59cf2 FD |
73 | }; |
74 | ||
f8017bfc PF |
75 | static bool in_hole(void *context, u32 index) |
76 | { | |
77 | struct ocotp_priv *priv = context; | |
78 | const struct ocotp_devtype_data *data = priv->data; | |
79 | int i; | |
80 | ||
81 | for (i = 0; i < data->num_region; i++) { | |
82 | if (data->region[i].flag & HOLE_REGION) { | |
83 | if ((index >= data->region[i].start) && | |
84 | (index <= data->region[i].end)) | |
85 | return true; | |
86 | } | |
87 | } | |
88 | ||
89 | return false; | |
90 | } | |
91 | ||
885ce72a PF |
92 | static bool in_ecc(void *context, u32 index) |
93 | { | |
94 | struct ocotp_priv *priv = context; | |
95 | const struct ocotp_devtype_data *data = priv->data; | |
96 | int i; | |
97 | ||
98 | for (i = 0; i < data->num_region; i++) { | |
99 | if (data->region[i].flag & ECC_REGION) { | |
100 | if ((index >= data->region[i].start) && | |
101 | (index <= data->region[i].end)) | |
102 | return true; | |
103 | } | |
104 | } | |
105 | ||
106 | return false; | |
107 | } | |
108 | ||
67ff708b PF |
109 | static int imx_sc_misc_otp_fuse_read(struct imx_sc_ipc *ipc, u32 word, |
110 | u32 *val) | |
111 | { | |
112 | struct imx_sc_msg_misc_fuse_read msg; | |
113 | struct imx_sc_rpc_msg *hdr = &msg.hdr; | |
114 | int ret; | |
115 | ||
116 | hdr->ver = IMX_SC_RPC_VERSION; | |
117 | hdr->svc = IMX_SC_RPC_SVC_MISC; | |
118 | hdr->func = IMX_SC_MISC_FUNC_OTP_FUSE_READ; | |
119 | hdr->size = 2; | |
120 | ||
121 | msg.word = word; | |
122 | ||
123 | ret = imx_scu_call_rpc(ipc, &msg, true); | |
124 | if (ret) | |
125 | return ret; | |
126 | ||
127 | *val = msg.word; | |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
132 | static int imx_scu_ocotp_read(void *context, unsigned int offset, | |
133 | void *val, size_t bytes) | |
134 | { | |
135 | struct ocotp_priv *priv = context; | |
136 | u32 count, index, num_bytes; | |
137 | u32 *buf; | |
138 | void *p; | |
139 | int i, ret; | |
140 | ||
8c4d35af PF |
141 | index = offset; |
142 | num_bytes = round_up(bytes, 4); | |
67ff708b PF |
143 | count = num_bytes >> 2; |
144 | ||
145 | if (count > (priv->data->nregs - index)) | |
146 | count = priv->data->nregs - index; | |
147 | ||
148 | p = kzalloc(num_bytes, GFP_KERNEL); | |
149 | if (!p) | |
150 | return -ENOMEM; | |
151 | ||
885ce72a PF |
152 | mutex_lock(&scu_ocotp_mutex); |
153 | ||
67ff708b PF |
154 | buf = p; |
155 | ||
156 | for (i = index; i < (index + count); i++) { | |
f8017bfc PF |
157 | if (in_hole(context, i)) { |
158 | *buf++ = 0; | |
159 | continue; | |
67ff708b PF |
160 | } |
161 | ||
162 | ret = imx_sc_misc_otp_fuse_read(priv->nvmem_ipc, i, buf); | |
163 | if (ret) { | |
885ce72a | 164 | mutex_unlock(&scu_ocotp_mutex); |
67ff708b PF |
165 | kfree(p); |
166 | return ret; | |
167 | } | |
168 | buf++; | |
169 | } | |
170 | ||
8c4d35af | 171 | memcpy(val, (u8 *)p, bytes); |
67ff708b | 172 | |
885ce72a PF |
173 | mutex_unlock(&scu_ocotp_mutex); |
174 | ||
67ff708b PF |
175 | kfree(p); |
176 | ||
177 | return 0; | |
178 | } | |
179 | ||
885ce72a PF |
180 | static int imx_scu_ocotp_write(void *context, unsigned int offset, |
181 | void *val, size_t bytes) | |
182 | { | |
183 | struct ocotp_priv *priv = context; | |
184 | struct arm_smccc_res res; | |
185 | u32 *buf = val; | |
186 | u32 tmp; | |
187 | u32 index; | |
188 | int ret; | |
189 | ||
190 | /* allow only writing one complete OTP word at a time */ | |
8c4d35af | 191 | if (bytes != 4) |
885ce72a PF |
192 | return -EINVAL; |
193 | ||
8c4d35af | 194 | index = offset; |
885ce72a PF |
195 | |
196 | if (in_hole(context, index)) | |
197 | return -EINVAL; | |
198 | ||
199 | if (in_ecc(context, index)) { | |
200 | pr_warn("ECC region, only program once\n"); | |
201 | mutex_lock(&scu_ocotp_mutex); | |
202 | ret = imx_sc_misc_otp_fuse_read(priv->nvmem_ipc, index, &tmp); | |
203 | mutex_unlock(&scu_ocotp_mutex); | |
204 | if (ret) | |
205 | return ret; | |
206 | if (tmp) { | |
207 | pr_warn("ECC region, already has value: %x\n", tmp); | |
208 | return -EIO; | |
209 | } | |
210 | } | |
211 | ||
212 | mutex_lock(&scu_ocotp_mutex); | |
213 | ||
40bb95db | 214 | arm_smccc_smc(IMX_SIP_OTP_WRITE, index, *buf, 0, 0, 0, 0, 0, &res); |
885ce72a PF |
215 | |
216 | mutex_unlock(&scu_ocotp_mutex); | |
217 | ||
218 | return res.a0; | |
219 | } | |
220 | ||
67ff708b PF |
221 | static struct nvmem_config imx_scu_ocotp_nvmem_config = { |
222 | .name = "imx-scu-ocotp", | |
885ce72a | 223 | .read_only = false, |
67ff708b PF |
224 | .word_size = 4, |
225 | .stride = 1, | |
226 | .owner = THIS_MODULE, | |
227 | .reg_read = imx_scu_ocotp_read, | |
885ce72a | 228 | .reg_write = imx_scu_ocotp_write, |
67ff708b PF |
229 | }; |
230 | ||
231 | static const struct of_device_id imx_scu_ocotp_dt_ids[] = { | |
232 | { .compatible = "fsl,imx8qxp-scu-ocotp", (void *)&imx8qxp_data }, | |
a3c59cf2 | 233 | { .compatible = "fsl,imx8qm-scu-ocotp", (void *)&imx8qm_data }, |
67ff708b PF |
234 | { }, |
235 | }; | |
236 | MODULE_DEVICE_TABLE(of, imx_scu_ocotp_dt_ids); | |
237 | ||
238 | static int imx_scu_ocotp_probe(struct platform_device *pdev) | |
239 | { | |
240 | struct device *dev = &pdev->dev; | |
241 | struct ocotp_priv *priv; | |
242 | struct nvmem_device *nvmem; | |
243 | int ret; | |
244 | ||
245 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
246 | if (!priv) | |
247 | return -ENOMEM; | |
248 | ||
249 | ret = imx_scu_get_handle(&priv->nvmem_ipc); | |
250 | if (ret) | |
251 | return ret; | |
252 | ||
253 | priv->data = of_device_get_match_data(dev); | |
254 | priv->dev = dev; | |
255 | imx_scu_ocotp_nvmem_config.size = 4 * priv->data->nregs; | |
256 | imx_scu_ocotp_nvmem_config.dev = dev; | |
257 | imx_scu_ocotp_nvmem_config.priv = priv; | |
258 | nvmem = devm_nvmem_register(dev, &imx_scu_ocotp_nvmem_config); | |
259 | ||
260 | return PTR_ERR_OR_ZERO(nvmem); | |
261 | } | |
262 | ||
263 | static struct platform_driver imx_scu_ocotp_driver = { | |
264 | .probe = imx_scu_ocotp_probe, | |
265 | .driver = { | |
266 | .name = "imx_scu_ocotp", | |
267 | .of_match_table = imx_scu_ocotp_dt_ids, | |
268 | }, | |
269 | }; | |
270 | module_platform_driver(imx_scu_ocotp_driver); | |
271 | ||
272 | MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); | |
273 | MODULE_DESCRIPTION("i.MX8 SCU OCOTP fuse box driver"); | |
274 | MODULE_LICENSE("GPL v2"); |