Loading...
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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | // SPDX-License-Identifier: GPL-2.0 /* * This is a driver for the eMemory EG004K32TQ028XW01 NeoFuse * One-Time-Programmable (OTP) memory used within the SiFive FU540. * It is documented in the FU540 manual here: * https://www.sifive.com/documentation/chips/freedom-u540-c000-manual/ * * Copyright (C) 2018 Philipp Hug <philipp@hug.cx> * Copyright (C) 2018 Joey Hewitt <joey@joeyhewitt.com> * * Copyright (C) 2020 SiFive, Inc */ /* * The FU540 stores 4096x32 bit (16KiB) values. * Index 0x00-0xff are reserved for SiFive internal use. (first 1KiB) * Right now first 1KiB is used to store only serial number. */ #include <dm/device.h> #include <dm/read.h> #include <linux/bitops.h> #include <linux/delay.h> #include <linux/io.h> #include <misc.h> #include <linux/printk.h> #define BYTES_PER_FUSE 4 #define PA_RESET_VAL 0x00 #define PAS_RESET_VAL 0x00 #define PAIO_RESET_VAL 0x00 #define PDIN_RESET_VAL 0x00 #define PTM_RESET_VAL 0x00 #define PCLK_ENABLE_VAL BIT(0) #define PCLK_DISABLE_VAL 0x00 #define PWE_WRITE_ENABLE BIT(0) #define PWE_WRITE_DISABLE 0x00 #define PTM_FUSE_PROGRAM_VAL BIT(1) #define PCE_ENABLE_INPUT BIT(0) #define PCE_DISABLE_INPUT 0x00 #define PPROG_ENABLE_INPUT BIT(0) #define PPROG_DISABLE_INPUT 0x00 #define PTRIM_ENABLE_INPUT BIT(0) #define PTRIM_DISABLE_INPUT 0x00 #define PDSTB_DEEP_STANDBY_ENABLE BIT(0) #define PDSTB_DEEP_STANDBY_DISABLE 0x00 /* Tpw - Program Pulse width delay */ #define TPW_DELAY 20 /* Tpwi - Program Pulse interval delay */ #define TPWI_DELAY 5 /* Tasp - Program address setup delay */ #define TASP_DELAY 1 /* Tcd - read data access delay */ #define TCD_DELAY 40 /* Tkl - clok pulse low delay */ #define TKL_DELAY 10 /* Tms - PTM mode setup delay */ #define TMS_DELAY 1 struct sifive_otp_regs { u32 pa; /* Address input */ u32 paio; /* Program address input */ u32 pas; /* Program redundancy cell selection input */ u32 pce; /* OTP Macro enable input */ u32 pclk; /* Clock input */ u32 pdin; /* Write data input */ u32 pdout; /* Read data output */ u32 pdstb; /* Deep standby mode enable input (active low) */ u32 pprog; /* Program mode enable input */ u32 ptc; /* Test column enable input */ u32 ptm; /* Test mode enable input */ u32 ptm_rep;/* Repair function test mode enable input */ u32 ptr; /* Test row enable input */ u32 ptrim; /* Repair function enable input */ u32 pwe; /* Write enable input (defines program cycle) */ }; struct sifive_otp_plat { struct sifive_otp_regs __iomem *regs; u32 total_fuses; }; /* * offset and size are assumed aligned to the size of the fuses (32-bit). */ static int sifive_otp_read(struct udevice *dev, int offset, void *buf, int size) { struct sifive_otp_plat *plat = dev_get_plat(dev); struct sifive_otp_regs *regs = (struct sifive_otp_regs *)plat->regs; /* Check if offset and size are multiple of BYTES_PER_FUSE */ if ((size % BYTES_PER_FUSE) || (offset % BYTES_PER_FUSE)) { printf("%s: size and offset must be multiple of 4.\n", __func__); return -EINVAL; } int fuseidx = offset / BYTES_PER_FUSE; int fusecount = size / BYTES_PER_FUSE; /* check bounds */ if (offset < 0 || size < 0) return -EINVAL; if (fuseidx >= plat->total_fuses) return -EINVAL; if ((fuseidx + fusecount) > plat->total_fuses) return -EINVAL; u32 fusebuf[fusecount]; /* init OTP */ writel(PDSTB_DEEP_STANDBY_ENABLE, ®s->pdstb); writel(PTRIM_ENABLE_INPUT, ®s->ptrim); writel(PCE_ENABLE_INPUT, ®s->pce); /* read all requested fuses */ for (unsigned int i = 0; i < fusecount; i++, fuseidx++) { writel(fuseidx, ®s->pa); /* cycle clock to read */ writel(PCLK_ENABLE_VAL, ®s->pclk); ndelay(TCD_DELAY * 1000); writel(PCLK_DISABLE_VAL, ®s->pclk); ndelay(TKL_DELAY * 1000); /* read the value */ fusebuf[i] = readl(®s->pdout); } /* shut down */ writel(PCE_DISABLE_INPUT, ®s->pce); writel(PTRIM_DISABLE_INPUT, ®s->ptrim); writel(PDSTB_DEEP_STANDBY_DISABLE, ®s->pdstb); /* copy out */ memcpy(buf, fusebuf, size); return size; } /* * Caution: * OTP can be written only once, so use carefully. * * offset and size are assumed aligned to the size of the fuses (32-bit). */ static int sifive_otp_write(struct udevice *dev, int offset, const void *buf, int size) { struct sifive_otp_plat *plat = dev_get_plat(dev); struct sifive_otp_regs *regs = (struct sifive_otp_regs *)plat->regs; /* Check if offset and size are multiple of BYTES_PER_FUSE */ if ((size % BYTES_PER_FUSE) || (offset % BYTES_PER_FUSE)) { printf("%s: size and offset must be multiple of 4.\n", __func__); return -EINVAL; } int fuseidx = offset / BYTES_PER_FUSE; int fusecount = size / BYTES_PER_FUSE; u32 *write_buf = (u32 *)buf; u32 write_data; int i, pas, bit; /* check bounds */ if (offset < 0 || size < 0) return -EINVAL; if (fuseidx >= plat->total_fuses) return -EINVAL; if ((fuseidx + fusecount) > plat->total_fuses) return -EINVAL; /* init OTP */ writel(PDSTB_DEEP_STANDBY_ENABLE, ®s->pdstb); writel(PTRIM_ENABLE_INPUT, ®s->ptrim); /* reset registers */ writel(PCLK_DISABLE_VAL, ®s->pclk); writel(PA_RESET_VAL, ®s->pa); writel(PAS_RESET_VAL, ®s->pas); writel(PAIO_RESET_VAL, ®s->paio); writel(PDIN_RESET_VAL, ®s->pdin); writel(PWE_WRITE_DISABLE, ®s->pwe); writel(PTM_FUSE_PROGRAM_VAL, ®s->ptm); ndelay(TMS_DELAY * 1000); writel(PCE_ENABLE_INPUT, ®s->pce); writel(PPROG_ENABLE_INPUT, ®s->pprog); /* write all requested fuses */ for (i = 0; i < fusecount; i++, fuseidx++) { writel(fuseidx, ®s->pa); write_data = *(write_buf++); for (pas = 0; pas < 2; pas++) { writel(pas, ®s->pas); for (bit = 0; bit < 32; bit++) { writel(bit, ®s->paio); writel(((write_data >> bit) & 1), ®s->pdin); ndelay(TASP_DELAY * 1000); writel(PWE_WRITE_ENABLE, ®s->pwe); udelay(TPW_DELAY); writel(PWE_WRITE_DISABLE, ®s->pwe); udelay(TPWI_DELAY); } } writel(PAS_RESET_VAL, ®s->pas); } /* shut down */ writel(PWE_WRITE_DISABLE, ®s->pwe); writel(PPROG_DISABLE_INPUT, ®s->pprog); writel(PCE_DISABLE_INPUT, ®s->pce); writel(PTM_RESET_VAL, ®s->ptm); writel(PTRIM_DISABLE_INPUT, ®s->ptrim); writel(PDSTB_DEEP_STANDBY_DISABLE, ®s->pdstb); return size; } static int sifive_otp_of_to_plat(struct udevice *dev) { struct sifive_otp_plat *plat = dev_get_plat(dev); int ret; plat->regs = dev_read_addr_ptr(dev); ret = dev_read_u32(dev, "fuse-count", &plat->total_fuses); if (ret < 0) { pr_err("\"fuse-count\" not found\n"); return ret; } return 0; } static const struct misc_ops sifive_otp_ops = { .read = sifive_otp_read, .write = sifive_otp_write, }; static const struct udevice_id sifive_otp_ids[] = { { .compatible = "sifive,fu540-c000-otp" }, {} }; U_BOOT_DRIVER(sifive_otp) = { .name = "sifive_otp", .id = UCLASS_MISC, .of_match = sifive_otp_ids, .of_to_plat = sifive_otp_of_to_plat, .plat_auto = sizeof(struct sifive_otp_plat), .ops = &sifive_otp_ops, }; |