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