Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
5c990456 PCM |
2 | /* |
3 | * Copyright (C) 2015 | |
4 | * Cristian Birsan <cristian.birsan@microchip.com> | |
5 | * Purna Chandra Mandal <purna.mandal@microchip.com> | |
5c990456 PCM |
6 | */ |
7 | ||
8 | #include <common.h> | |
1eb69ae4 | 9 | #include <cpu_func.h> |
5c990456 PCM |
10 | #include <dm.h> |
11 | #include <fdt_support.h> | |
12 | #include <flash.h> | |
691d719d | 13 | #include <init.h> |
36bf446b | 14 | #include <irq_func.h> |
5c990456 PCM |
15 | #include <mach/pic32.h> |
16 | #include <wait_bit.h> | |
17 | ||
18 | DECLARE_GLOBAL_DATA_PTR; | |
19 | ||
20 | /* NVM Controller registers */ | |
21 | struct pic32_reg_nvm { | |
22 | struct pic32_reg_atomic ctrl; | |
23 | struct pic32_reg_atomic key; | |
24 | struct pic32_reg_atomic addr; | |
25 | struct pic32_reg_atomic data; | |
26 | }; | |
27 | ||
28 | /* NVM operations */ | |
29 | #define NVMOP_NOP 0 | |
30 | #define NVMOP_WORD_WRITE 1 | |
31 | #define NVMOP_PAGE_ERASE 4 | |
32 | ||
33 | /* NVM control bits */ | |
34 | #define NVM_WR BIT(15) | |
35 | #define NVM_WREN BIT(14) | |
36 | #define NVM_WRERR BIT(13) | |
37 | #define NVM_LVDERR BIT(12) | |
38 | ||
39 | /* NVM programming unlock register */ | |
40 | #define LOCK_KEY 0x0 | |
41 | #define UNLOCK_KEY1 0xaa996655 | |
42 | #define UNLOCK_KEY2 0x556699aa | |
43 | ||
44 | /* | |
45 | * PIC32 flash banks consist of number of pages, each page | |
46 | * into number of rows and rows into number of words. | |
47 | * Here we will maintain page information instead of sector. | |
48 | */ | |
49 | flash_info_t flash_info[CONFIG_SYS_MAX_FLASH_BANKS]; | |
50 | static struct pic32_reg_nvm *nvm_regs_p; | |
51 | ||
52 | static inline void flash_initiate_operation(u32 nvmop) | |
53 | { | |
54 | /* set operation */ | |
55 | writel(nvmop, &nvm_regs_p->ctrl.raw); | |
56 | ||
57 | /* enable flash write */ | |
58 | writel(NVM_WREN, &nvm_regs_p->ctrl.set); | |
59 | ||
60 | /* unlock sequence */ | |
61 | writel(LOCK_KEY, &nvm_regs_p->key.raw); | |
62 | writel(UNLOCK_KEY1, &nvm_regs_p->key.raw); | |
63 | writel(UNLOCK_KEY2, &nvm_regs_p->key.raw); | |
64 | ||
65 | /* initiate operation */ | |
66 | writel(NVM_WR, &nvm_regs_p->ctrl.set); | |
67 | } | |
68 | ||
69 | static int flash_wait_till_busy(const char *func, ulong timeout) | |
70 | { | |
48263504 ÁFR |
71 | int ret = wait_for_bit_le32(&nvm_regs_p->ctrl.raw, |
72 | NVM_WR, false, timeout, false); | |
5c990456 | 73 | |
9dbaebcf | 74 | return ret ? ERR_TIMEOUT : ERR_OK; |
5c990456 PCM |
75 | } |
76 | ||
77 | static inline int flash_complete_operation(void) | |
78 | { | |
79 | u32 tmp; | |
80 | ||
81 | tmp = readl(&nvm_regs_p->ctrl.raw); | |
82 | if (tmp & NVM_WRERR) { | |
83 | printf("Error in Block Erase - Lock Bit may be set!\n"); | |
84 | flash_initiate_operation(NVMOP_NOP); | |
85 | return ERR_PROTECTED; | |
86 | } | |
87 | ||
88 | if (tmp & NVM_LVDERR) { | |
89 | printf("Error in Block Erase - low-vol detected!\n"); | |
90 | flash_initiate_operation(NVMOP_NOP); | |
91 | return ERR_NOT_ERASED; | |
92 | } | |
93 | ||
94 | /* disable flash write or erase operation */ | |
95 | writel(NVM_WREN, &nvm_regs_p->ctrl.clr); | |
96 | ||
97 | return ERR_OK; | |
98 | } | |
99 | ||
100 | /* | |
101 | * Erase flash sectors, returns: | |
102 | * ERR_OK - OK | |
103 | * ERR_INVAL - invalid sector arguments | |
9dbaebcf | 104 | * ERR_TIMEOUT - write timeout |
5c990456 PCM |
105 | * ERR_NOT_ERASED - Flash not erased |
106 | * ERR_UNKNOWN_FLASH_VENDOR - incorrect flash | |
107 | */ | |
108 | int flash_erase(flash_info_t *info, int s_first, int s_last) | |
109 | { | |
110 | ulong sect_start, sect_end, flags; | |
111 | int prot, sect; | |
112 | int rc; | |
113 | ||
114 | if ((info->flash_id & FLASH_VENDMASK) != FLASH_MAN_MCHP) { | |
115 | printf("Can't erase unknown flash type %08lx - aborted\n", | |
116 | info->flash_id); | |
117 | return ERR_UNKNOWN_FLASH_VENDOR; | |
118 | } | |
119 | ||
120 | if ((s_first < 0) || (s_first > s_last)) { | |
121 | printf("- no sectors to erase\n"); | |
122 | return ERR_INVAL; | |
123 | } | |
124 | ||
125 | prot = 0; | |
126 | for (sect = s_first; sect <= s_last; ++sect) { | |
127 | if (info->protect[sect]) | |
128 | prot++; | |
129 | } | |
130 | ||
131 | if (prot) | |
132 | printf("- Warning: %d protected sectors will not be erased!\n", | |
133 | prot); | |
134 | else | |
135 | printf("\n"); | |
136 | ||
137 | /* erase on unprotected sectors */ | |
138 | for (sect = s_first; sect <= s_last; sect++) { | |
139 | if (info->protect[sect]) | |
140 | continue; | |
141 | ||
142 | /* disable interrupts */ | |
143 | flags = disable_interrupts(); | |
144 | ||
145 | /* write destination page address (physical) */ | |
146 | sect_start = CPHYSADDR(info->start[sect]); | |
147 | writel(sect_start, &nvm_regs_p->addr.raw); | |
148 | ||
149 | /* page erase */ | |
150 | flash_initiate_operation(NVMOP_PAGE_ERASE); | |
151 | ||
152 | /* wait */ | |
153 | rc = flash_wait_till_busy(__func__, | |
154 | CONFIG_SYS_FLASH_ERASE_TOUT); | |
155 | ||
156 | /* re-enable interrupts if necessary */ | |
157 | if (flags) | |
158 | enable_interrupts(); | |
159 | ||
160 | if (rc != ERR_OK) | |
161 | return rc; | |
162 | ||
163 | rc = flash_complete_operation(); | |
164 | if (rc != ERR_OK) | |
165 | return rc; | |
166 | ||
167 | /* | |
168 | * flash content is updated but cache might contain stale | |
169 | * data, so invalidate dcache. | |
170 | */ | |
171 | sect_end = info->start[sect] + info->size / info->sector_count; | |
172 | invalidate_dcache_range(info->start[sect], sect_end); | |
173 | } | |
174 | ||
175 | printf(" done\n"); | |
176 | return ERR_OK; | |
177 | } | |
178 | ||
179 | int page_erase(flash_info_t *info, int sect) | |
180 | { | |
181 | return 0; | |
182 | } | |
183 | ||
184 | /* Write a word to flash */ | |
185 | static int write_word(flash_info_t *info, ulong dest, ulong word) | |
186 | { | |
187 | ulong flags; | |
188 | int rc; | |
189 | ||
190 | /* read flash to check if it is sufficiently erased */ | |
191 | if ((readl((void __iomem *)dest) & word) != word) { | |
192 | printf("Error, Flash not erased!\n"); | |
193 | return ERR_NOT_ERASED; | |
194 | } | |
195 | ||
196 | /* disable interrupts */ | |
197 | flags = disable_interrupts(); | |
198 | ||
199 | /* update destination page address (physical) */ | |
200 | writel(CPHYSADDR(dest), &nvm_regs_p->addr.raw); | |
201 | writel(word, &nvm_regs_p->data.raw); | |
202 | ||
203 | /* word write */ | |
204 | flash_initiate_operation(NVMOP_WORD_WRITE); | |
205 | ||
206 | /* wait for operation to complete */ | |
207 | rc = flash_wait_till_busy(__func__, CONFIG_SYS_FLASH_WRITE_TOUT); | |
208 | ||
209 | /* re-enable interrupts if necessary */ | |
210 | if (flags) | |
211 | enable_interrupts(); | |
212 | ||
213 | if (rc != ERR_OK) | |
214 | return rc; | |
215 | ||
216 | return flash_complete_operation(); | |
217 | } | |
218 | ||
219 | /* | |
220 | * Copy memory to flash, returns: | |
221 | * ERR_OK - OK | |
9dbaebcf | 222 | * ERR_TIMEOUT - write timeout |
5c990456 PCM |
223 | * ERR_NOT_ERASED - Flash not erased |
224 | */ | |
225 | int write_buff(flash_info_t *info, uchar *src, ulong addr, ulong cnt) | |
226 | { | |
227 | ulong dst, tmp_le, len = cnt; | |
228 | int i, l, rc; | |
229 | uchar *cp; | |
230 | ||
231 | /* get lower word aligned address */ | |
232 | dst = (addr & ~3); | |
233 | ||
234 | /* handle unaligned start bytes */ | |
235 | l = addr - dst; | |
236 | if (l != 0) { | |
237 | tmp_le = 0; | |
238 | for (i = 0, cp = (uchar *)dst; i < l; ++i, ++cp) | |
239 | tmp_le |= *cp << (i * 8); | |
240 | ||
241 | for (; (i < 4) && (cnt > 0); ++i, ++src, --cnt, ++cp) | |
242 | tmp_le |= *src << (i * 8); | |
243 | ||
244 | for (; (cnt == 0) && (i < 4); ++i, ++cp) | |
245 | tmp_le |= *cp << (i * 8); | |
246 | ||
247 | rc = write_word(info, dst, tmp_le); | |
248 | if (rc) | |
249 | goto out; | |
250 | ||
251 | dst += 4; | |
252 | } | |
253 | ||
254 | /* handle word aligned part */ | |
255 | while (cnt >= 4) { | |
256 | tmp_le = src[0] | src[1] << 8 | src[2] << 16 | src[3] << 24; | |
257 | rc = write_word(info, dst, tmp_le); | |
258 | if (rc) | |
259 | goto out; | |
260 | src += 4; | |
261 | dst += 4; | |
262 | cnt -= 4; | |
263 | } | |
264 | ||
265 | if (cnt == 0) { | |
266 | rc = ERR_OK; | |
267 | goto out; | |
268 | } | |
269 | ||
270 | /* handle unaligned tail bytes */ | |
271 | tmp_le = 0; | |
272 | for (i = 0, cp = (uchar *)dst; (i < 4) && (cnt > 0); ++i, ++cp) { | |
273 | tmp_le |= *src++ << (i * 8); | |
274 | --cnt; | |
275 | } | |
276 | ||
277 | for (; i < 4; ++i, ++cp) | |
278 | tmp_le |= *cp << (i * 8); | |
279 | ||
280 | rc = write_word(info, dst, tmp_le); | |
281 | out: | |
282 | /* | |
283 | * flash content updated by nvm controller but CPU cache might | |
284 | * have stale data, so invalidate dcache. | |
285 | */ | |
286 | invalidate_dcache_range(addr, addr + len); | |
287 | ||
288 | printf(" done\n"); | |
289 | return rc; | |
290 | } | |
291 | ||
292 | void flash_print_info(flash_info_t *info) | |
293 | { | |
294 | int i; | |
295 | ||
296 | if (info->flash_id == FLASH_UNKNOWN) { | |
297 | printf("missing or unknown FLASH type\n"); | |
298 | return; | |
299 | } | |
300 | ||
301 | switch (info->flash_id & FLASH_VENDMASK) { | |
302 | case FLASH_MAN_MCHP: | |
303 | printf("Microchip Technology "); | |
304 | break; | |
305 | default: | |
306 | printf("Unknown Vendor "); | |
307 | break; | |
308 | } | |
309 | ||
310 | switch (info->flash_id & FLASH_TYPEMASK) { | |
311 | case FLASH_MCHP100T: | |
312 | printf("Internal (8 Mbit, 64 x 16k)\n"); | |
313 | break; | |
314 | default: | |
315 | printf("Unknown Chip Type\n"); | |
316 | break; | |
317 | } | |
318 | ||
319 | printf(" Size: %ld MB in %d Sectors\n", | |
320 | info->size >> 20, info->sector_count); | |
321 | ||
322 | printf(" Sector Start Addresses:"); | |
323 | for (i = 0; i < info->sector_count; ++i) { | |
324 | if ((i % 5) == 0) | |
325 | printf("\n "); | |
326 | ||
327 | printf(" %08lX%s", info->start[i], | |
328 | info->protect[i] ? " (RO)" : " "); | |
329 | } | |
330 | printf("\n"); | |
331 | } | |
332 | ||
333 | unsigned long flash_init(void) | |
334 | { | |
335 | unsigned long size = 0; | |
336 | struct udevice *dev; | |
337 | int bank; | |
338 | ||
339 | /* probe every MTD device */ | |
340 | for (uclass_first_device(UCLASS_MTD, &dev); dev; | |
341 | uclass_next_device(&dev)) { | |
342 | /* nop */ | |
343 | } | |
344 | ||
345 | /* calc total flash size */ | |
346 | for (bank = 0; bank < CONFIG_SYS_MAX_FLASH_BANKS; ++bank) | |
347 | size += flash_info[bank].size; | |
348 | ||
349 | return size; | |
350 | } | |
351 | ||
352 | static void pic32_flash_bank_init(flash_info_t *info, | |
353 | ulong base, ulong size) | |
354 | { | |
355 | ulong sect_size; | |
356 | int sect; | |
357 | ||
358 | /* device & manufacturer code */ | |
359 | info->flash_id = FLASH_MAN_MCHP | FLASH_MCHP100T; | |
360 | info->sector_count = CONFIG_SYS_MAX_FLASH_SECT; | |
361 | info->size = size; | |
362 | ||
363 | /* update sector (i.e page) info */ | |
364 | sect_size = info->size / info->sector_count; | |
365 | for (sect = 0; sect < info->sector_count; sect++) { | |
366 | info->start[sect] = base; | |
367 | /* protect each sector by default */ | |
368 | info->protect[sect] = 1; | |
369 | base += sect_size; | |
370 | } | |
371 | } | |
372 | ||
373 | static int pic32_flash_probe(struct udevice *dev) | |
374 | { | |
375 | void *blob = (void *)gd->fdt_blob; | |
e160f7d4 | 376 | int node = dev_of_offset(dev); |
5c990456 PCM |
377 | const char *list, *end; |
378 | const fdt32_t *cell; | |
379 | unsigned long addr, size; | |
380 | int parent, addrc, sizec; | |
381 | flash_info_t *info; | |
382 | int len, idx; | |
383 | ||
384 | /* | |
385 | * decode regs. there are multiple reg tuples, and they need to | |
386 | * match with reg-names. | |
387 | */ | |
388 | parent = fdt_parent_offset(blob, node); | |
eed36609 | 389 | fdt_support_default_count_cells(blob, parent, &addrc, &sizec); |
5c990456 PCM |
390 | list = fdt_getprop(blob, node, "reg-names", &len); |
391 | if (!list) | |
392 | return -ENOENT; | |
393 | ||
394 | end = list + len; | |
395 | cell = fdt_getprop(blob, node, "reg", &len); | |
396 | if (!cell) | |
397 | return -ENOENT; | |
398 | ||
399 | for (idx = 0, info = &flash_info[0]; list < end;) { | |
400 | addr = fdt_translate_address((void *)blob, node, cell + idx); | |
401 | size = fdt_addr_to_cpu(cell[idx + addrc]); | |
402 | len = strlen(list); | |
403 | if (!strncmp(list, "nvm", len)) { | |
404 | /* NVM controller */ | |
405 | nvm_regs_p = ioremap(addr, size); | |
406 | } else if (!strncmp(list, "bank", 4)) { | |
407 | /* Flash bank: use kseg0 cached address */ | |
408 | pic32_flash_bank_init(info, CKSEG0ADDR(addr), size); | |
409 | info++; | |
410 | } | |
411 | idx += addrc + sizec; | |
412 | list += len + 1; | |
413 | } | |
414 | ||
415 | /* disable flash write/erase operations */ | |
416 | writel(NVM_WREN, &nvm_regs_p->ctrl.clr); | |
417 | ||
418 | #if (CONFIG_SYS_MONITOR_BASE >= CONFIG_SYS_FLASH_BASE) | |
419 | /* monitor protection ON by default */ | |
420 | flash_protect(FLAG_PROTECT_SET, | |
421 | CONFIG_SYS_MONITOR_BASE, | |
422 | CONFIG_SYS_MONITOR_BASE + monitor_flash_len - 1, | |
423 | &flash_info[0]); | |
424 | #endif | |
425 | ||
426 | #ifdef CONFIG_ENV_IS_IN_FLASH | |
427 | /* ENV protection ON by default */ | |
428 | flash_protect(FLAG_PROTECT_SET, | |
429 | CONFIG_ENV_ADDR, | |
430 | CONFIG_ENV_ADDR + CONFIG_ENV_SECT_SIZE - 1, | |
431 | &flash_info[0]); | |
432 | #endif | |
433 | return 0; | |
434 | } | |
435 | ||
436 | static const struct udevice_id pic32_flash_ids[] = { | |
437 | { .compatible = "microchip,pic32mzda-flash" }, | |
438 | {} | |
439 | }; | |
440 | ||
441 | U_BOOT_DRIVER(pic32_flash) = { | |
442 | .name = "pic32_flash", | |
443 | .id = UCLASS_MTD, | |
444 | .of_match = pic32_flash_ids, | |
445 | .probe = pic32_flash_probe, | |
446 | }; |