blob: 3e658b3566292e14e5de5446158e10d4fb162cde [file] [log] [blame]
Pragnesh Patel05307212020-05-29 11:33:21 +05301// SPDX-License-Identifier: GPL-2.0
2/*
3 * This is a driver for the eMemory EG004K32TQ028XW01 NeoFuse
4 * One-Time-Programmable (OTP) memory used within the SiFive FU540.
5 * It is documented in the FU540 manual here:
6 * https://www.sifive.com/documentation/chips/freedom-u540-c000-manual/
7 *
8 * Copyright (C) 2018 Philipp Hug <philipp@hug.cx>
9 * Copyright (C) 2018 Joey Hewitt <joey@joeyhewitt.com>
10 *
11 * Copyright (C) 2020 SiFive, Inc
12 */
13
14/*
15 * The FU540 stores 4096x32 bit (16KiB) values.
16 * Index 0x00-0xff are reserved for SiFive internal use. (first 1KiB)
17 * Right now first 1KiB is used to store only serial number.
18 */
19
20#include <common.h>
21#include <dm/device.h>
22#include <dm/read.h>
23#include <linux/bitops.h>
24#include <linux/delay.h>
25#include <linux/io.h>
26#include <misc.h>
27
28#define BYTES_PER_FUSE 4
29
30#define PA_RESET_VAL 0x00
31#define PAS_RESET_VAL 0x00
32#define PAIO_RESET_VAL 0x00
33#define PDIN_RESET_VAL 0x00
34#define PTM_RESET_VAL 0x00
35
36#define PCLK_ENABLE_VAL BIT(0)
37#define PCLK_DISABLE_VAL 0x00
38
39#define PWE_WRITE_ENABLE BIT(0)
40#define PWE_WRITE_DISABLE 0x00
41
42#define PTM_FUSE_PROGRAM_VAL BIT(1)
43
44#define PCE_ENABLE_INPUT BIT(0)
45#define PCE_DISABLE_INPUT 0x00
46
47#define PPROG_ENABLE_INPUT BIT(0)
48#define PPROG_DISABLE_INPUT 0x00
49
50#define PTRIM_ENABLE_INPUT BIT(0)
51#define PTRIM_DISABLE_INPUT 0x00
52
53#define PDSTB_DEEP_STANDBY_ENABLE BIT(0)
54#define PDSTB_DEEP_STANDBY_DISABLE 0x00
55
56/* Tpw - Program Pulse width delay */
57#define TPW_DELAY 20
58
59/* Tpwi - Program Pulse interval delay */
60#define TPWI_DELAY 5
61
62/* Tasp - Program address setup delay */
63#define TASP_DELAY 1
64
65/* Tcd - read data access delay */
66#define TCD_DELAY 40
67
68/* Tkl - clok pulse low delay */
69#define TKL_DELAY 10
70
71/* Tms - PTM mode setup delay */
72#define TMS_DELAY 1
73
74struct sifive_otp_regs {
75 u32 pa; /* Address input */
76 u32 paio; /* Program address input */
77 u32 pas; /* Program redundancy cell selection input */
78 u32 pce; /* OTP Macro enable input */
79 u32 pclk; /* Clock input */
80 u32 pdin; /* Write data input */
81 u32 pdout; /* Read data output */
82 u32 pdstb; /* Deep standby mode enable input (active low) */
83 u32 pprog; /* Program mode enable input */
84 u32 ptc; /* Test column enable input */
85 u32 ptm; /* Test mode enable input */
86 u32 ptm_rep;/* Repair function test mode enable input */
87 u32 ptr; /* Test row enable input */
88 u32 ptrim; /* Repair function enable input */
89 u32 pwe; /* Write enable input (defines program cycle) */
90};
91
Simon Glass8a8d24b2020-12-03 16:55:23 -070092struct sifive_otp_plat {
Pragnesh Patel05307212020-05-29 11:33:21 +053093 struct sifive_otp_regs __iomem *regs;
94 u32 total_fuses;
95};
96
97/*
98 * offset and size are assumed aligned to the size of the fuses (32-bit).
99 */
100static int sifive_otp_read(struct udevice *dev, int offset,
101 void *buf, int size)
102{
Simon Glass8a8d24b2020-12-03 16:55:23 -0700103 struct sifive_otp_plat *plat = dev_get_plat(dev);
Pragnesh Patel05307212020-05-29 11:33:21 +0530104 struct sifive_otp_regs *regs = (struct sifive_otp_regs *)plat->regs;
105
106 /* Check if offset and size are multiple of BYTES_PER_FUSE */
107 if ((size % BYTES_PER_FUSE) || (offset % BYTES_PER_FUSE)) {
108 printf("%s: size and offset must be multiple of 4.\n",
109 __func__);
110 return -EINVAL;
111 }
112
113 int fuseidx = offset / BYTES_PER_FUSE;
114 int fusecount = size / BYTES_PER_FUSE;
115
116 /* check bounds */
117 if (offset < 0 || size < 0)
118 return -EINVAL;
119 if (fuseidx >= plat->total_fuses)
120 return -EINVAL;
121 if ((fuseidx + fusecount) > plat->total_fuses)
122 return -EINVAL;
123
124 u32 fusebuf[fusecount];
125
126 /* init OTP */
127 writel(PDSTB_DEEP_STANDBY_ENABLE, &regs->pdstb);
128 writel(PTRIM_ENABLE_INPUT, &regs->ptrim);
129 writel(PCE_ENABLE_INPUT, &regs->pce);
130
131 /* read all requested fuses */
132 for (unsigned int i = 0; i < fusecount; i++, fuseidx++) {
133 writel(fuseidx, &regs->pa);
134
135 /* cycle clock to read */
136 writel(PCLK_ENABLE_VAL, &regs->pclk);
137 ndelay(TCD_DELAY * 1000);
138 writel(PCLK_DISABLE_VAL, &regs->pclk);
139 ndelay(TKL_DELAY * 1000);
140
141 /* read the value */
142 fusebuf[i] = readl(&regs->pdout);
143 }
144
145 /* shut down */
146 writel(PCE_DISABLE_INPUT, &regs->pce);
147 writel(PTRIM_DISABLE_INPUT, &regs->ptrim);
148 writel(PDSTB_DEEP_STANDBY_DISABLE, &regs->pdstb);
149
150 /* copy out */
151 memcpy(buf, fusebuf, size);
152
153 return size;
154}
155
156/*
157 * Caution:
158 * OTP can be written only once, so use carefully.
159 *
160 * offset and size are assumed aligned to the size of the fuses (32-bit).
161 */
162static int sifive_otp_write(struct udevice *dev, int offset,
163 const void *buf, int size)
164{
Simon Glass8a8d24b2020-12-03 16:55:23 -0700165 struct sifive_otp_plat *plat = dev_get_plat(dev);
Pragnesh Patel05307212020-05-29 11:33:21 +0530166 struct sifive_otp_regs *regs = (struct sifive_otp_regs *)plat->regs;
167
168 /* Check if offset and size are multiple of BYTES_PER_FUSE */
169 if ((size % BYTES_PER_FUSE) || (offset % BYTES_PER_FUSE)) {
170 printf("%s: size and offset must be multiple of 4.\n",
171 __func__);
172 return -EINVAL;
173 }
174
175 int fuseidx = offset / BYTES_PER_FUSE;
176 int fusecount = size / BYTES_PER_FUSE;
177 u32 *write_buf = (u32 *)buf;
178 u32 write_data;
179 int i, pas, bit;
180
181 /* check bounds */
182 if (offset < 0 || size < 0)
183 return -EINVAL;
184 if (fuseidx >= plat->total_fuses)
185 return -EINVAL;
186 if ((fuseidx + fusecount) > plat->total_fuses)
187 return -EINVAL;
188
189 /* init OTP */
190 writel(PDSTB_DEEP_STANDBY_ENABLE, &regs->pdstb);
191 writel(PTRIM_ENABLE_INPUT, &regs->ptrim);
192
193 /* reset registers */
194 writel(PCLK_DISABLE_VAL, &regs->pclk);
195 writel(PA_RESET_VAL, &regs->pa);
196 writel(PAS_RESET_VAL, &regs->pas);
197 writel(PAIO_RESET_VAL, &regs->paio);
198 writel(PDIN_RESET_VAL, &regs->pdin);
199 writel(PWE_WRITE_DISABLE, &regs->pwe);
200 writel(PTM_FUSE_PROGRAM_VAL, &regs->ptm);
201 ndelay(TMS_DELAY * 1000);
202
203 writel(PCE_ENABLE_INPUT, &regs->pce);
204 writel(PPROG_ENABLE_INPUT, &regs->pprog);
205
206 /* write all requested fuses */
207 for (i = 0; i < fusecount; i++, fuseidx++) {
208 writel(fuseidx, &regs->pa);
209 write_data = *(write_buf++);
210
211 for (pas = 0; pas < 2; pas++) {
212 writel(pas, &regs->pas);
213
214 for (bit = 0; bit < 32; bit++) {
215 writel(bit, &regs->paio);
216 writel(((write_data >> bit) & 1),
217 &regs->pdin);
218 ndelay(TASP_DELAY * 1000);
219
220 writel(PWE_WRITE_ENABLE, &regs->pwe);
221 udelay(TPW_DELAY);
222 writel(PWE_WRITE_DISABLE, &regs->pwe);
223 udelay(TPWI_DELAY);
224 }
225 }
226
227 writel(PAS_RESET_VAL, &regs->pas);
228 }
229
230 /* shut down */
231 writel(PWE_WRITE_DISABLE, &regs->pwe);
232 writel(PPROG_DISABLE_INPUT, &regs->pprog);
233 writel(PCE_DISABLE_INPUT, &regs->pce);
234 writel(PTM_RESET_VAL, &regs->ptm);
235
236 writel(PTRIM_DISABLE_INPUT, &regs->ptrim);
237 writel(PDSTB_DEEP_STANDBY_DISABLE, &regs->pdstb);
238
239 return size;
240}
241
Simon Glassd1998a92020-12-03 16:55:21 -0700242static int sifive_otp_of_to_plat(struct udevice *dev)
Pragnesh Patel05307212020-05-29 11:33:21 +0530243{
Simon Glass8a8d24b2020-12-03 16:55:23 -0700244 struct sifive_otp_plat *plat = dev_get_plat(dev);
Pragnesh Patel05307212020-05-29 11:33:21 +0530245 int ret;
246
247 plat->regs = dev_read_addr_ptr(dev);
248
249 ret = dev_read_u32(dev, "fuse-count", &plat->total_fuses);
250 if (ret < 0) {
251 pr_err("\"fuse-count\" not found\n");
252 return ret;
253 }
254
255 return 0;
256}
257
258static const struct misc_ops sifive_otp_ops = {
259 .read = sifive_otp_read,
260 .write = sifive_otp_write,
261};
262
263static const struct udevice_id sifive_otp_ids[] = {
264 { .compatible = "sifive,fu540-c000-otp" },
265 {}
266};
267
268U_BOOT_DRIVER(sifive_otp) = {
269 .name = "sifive_otp",
270 .id = UCLASS_MISC,
271 .of_match = sifive_otp_ids,
Simon Glassd1998a92020-12-03 16:55:21 -0700272 .of_to_plat = sifive_otp_of_to_plat,
Simon Glass8a8d24b2020-12-03 16:55:23 -0700273 .plat_auto = sizeof(struct sifive_otp_plat),
Pragnesh Patel05307212020-05-29 11:33:21 +0530274 .ops = &sifive_otp_ops,
275};