| /* | |
| * Copyright (C) 2015 | |
| * Cristian Birsan <cristian.birsan@microchip.com> | |
| * Purna Chandra Mandal <purna.mandal@microchip.com> | |
| * | |
| * SPDX-License-Identifier: GPL-2.0+ | |
| */ | |
| #include <common.h> | |
| #include <dm.h> | |
| #include <fdt_support.h> | |
| #include <flash.h> | |
| #include <mach/pic32.h> | |
| #include <wait_bit.h> | |
| DECLARE_GLOBAL_DATA_PTR; | |
| /* NVM Controller registers */ | |
| struct pic32_reg_nvm { | |
| struct pic32_reg_atomic ctrl; | |
| struct pic32_reg_atomic key; | |
| struct pic32_reg_atomic addr; | |
| struct pic32_reg_atomic data; | |
| }; | |
| /* NVM operations */ | |
| #define NVMOP_NOP 0 | |
| #define NVMOP_WORD_WRITE 1 | |
| #define NVMOP_PAGE_ERASE 4 | |
| /* NVM control bits */ | |
| #define NVM_WR BIT(15) | |
| #define NVM_WREN BIT(14) | |
| #define NVM_WRERR BIT(13) | |
| #define NVM_LVDERR BIT(12) | |
| /* NVM programming unlock register */ | |
| #define LOCK_KEY 0x0 | |
| #define UNLOCK_KEY1 0xaa996655 | |
| #define UNLOCK_KEY2 0x556699aa | |
| /* | |
| * PIC32 flash banks consist of number of pages, each page | |
| * into number of rows and rows into number of words. | |
| * Here we will maintain page information instead of sector. | |
| */ | |
| flash_info_t flash_info[CONFIG_SYS_MAX_FLASH_BANKS]; | |
| static struct pic32_reg_nvm *nvm_regs_p; | |
| static inline void flash_initiate_operation(u32 nvmop) | |
| { | |
| /* set operation */ | |
| writel(nvmop, &nvm_regs_p->ctrl.raw); | |
| /* enable flash write */ | |
| writel(NVM_WREN, &nvm_regs_p->ctrl.set); | |
| /* unlock sequence */ | |
| writel(LOCK_KEY, &nvm_regs_p->key.raw); | |
| writel(UNLOCK_KEY1, &nvm_regs_p->key.raw); | |
| writel(UNLOCK_KEY2, &nvm_regs_p->key.raw); | |
| /* initiate operation */ | |
| writel(NVM_WR, &nvm_regs_p->ctrl.set); | |
| } | |
| static int flash_wait_till_busy(const char *func, ulong timeout) | |
| { | |
| int ret = wait_for_bit(__func__, &nvm_regs_p->ctrl.raw, | |
| NVM_WR, false, timeout, false); | |
| return ret ? ERR_TIMOUT : ERR_OK; | |
| } | |
| static inline int flash_complete_operation(void) | |
| { | |
| u32 tmp; | |
| tmp = readl(&nvm_regs_p->ctrl.raw); | |
| if (tmp & NVM_WRERR) { | |
| printf("Error in Block Erase - Lock Bit may be set!\n"); | |
| flash_initiate_operation(NVMOP_NOP); | |
| return ERR_PROTECTED; | |
| } | |
| if (tmp & NVM_LVDERR) { | |
| printf("Error in Block Erase - low-vol detected!\n"); | |
| flash_initiate_operation(NVMOP_NOP); | |
| return ERR_NOT_ERASED; | |
| } | |
| /* disable flash write or erase operation */ | |
| writel(NVM_WREN, &nvm_regs_p->ctrl.clr); | |
| return ERR_OK; | |
| } | |
| /* | |
| * Erase flash sectors, returns: | |
| * ERR_OK - OK | |
| * ERR_INVAL - invalid sector arguments | |
| * ERR_TIMOUT - write timeout | |
| * ERR_NOT_ERASED - Flash not erased | |
| * ERR_UNKNOWN_FLASH_VENDOR - incorrect flash | |
| */ | |
| int flash_erase(flash_info_t *info, int s_first, int s_last) | |
| { | |
| ulong sect_start, sect_end, flags; | |
| int prot, sect; | |
| int rc; | |
| if ((info->flash_id & FLASH_VENDMASK) != FLASH_MAN_MCHP) { | |
| printf("Can't erase unknown flash type %08lx - aborted\n", | |
| info->flash_id); | |
| return ERR_UNKNOWN_FLASH_VENDOR; | |
| } | |
| if ((s_first < 0) || (s_first > s_last)) { | |
| printf("- no sectors to erase\n"); | |
| return ERR_INVAL; | |
| } | |
| prot = 0; | |
| for (sect = s_first; sect <= s_last; ++sect) { | |
| if (info->protect[sect]) | |
| prot++; | |
| } | |
| if (prot) | |
| printf("- Warning: %d protected sectors will not be erased!\n", | |
| prot); | |
| else | |
| printf("\n"); | |
| /* erase on unprotected sectors */ | |
| for (sect = s_first; sect <= s_last; sect++) { | |
| if (info->protect[sect]) | |
| continue; | |
| /* disable interrupts */ | |
| flags = disable_interrupts(); | |
| /* write destination page address (physical) */ | |
| sect_start = CPHYSADDR(info->start[sect]); | |
| writel(sect_start, &nvm_regs_p->addr.raw); | |
| /* page erase */ | |
| flash_initiate_operation(NVMOP_PAGE_ERASE); | |
| /* wait */ | |
| rc = flash_wait_till_busy(__func__, | |
| CONFIG_SYS_FLASH_ERASE_TOUT); | |
| /* re-enable interrupts if necessary */ | |
| if (flags) | |
| enable_interrupts(); | |
| if (rc != ERR_OK) | |
| return rc; | |
| rc = flash_complete_operation(); | |
| if (rc != ERR_OK) | |
| return rc; | |
| /* | |
| * flash content is updated but cache might contain stale | |
| * data, so invalidate dcache. | |
| */ | |
| sect_end = info->start[sect] + info->size / info->sector_count; | |
| invalidate_dcache_range(info->start[sect], sect_end); | |
| } | |
| printf(" done\n"); | |
| return ERR_OK; | |
| } | |
| int page_erase(flash_info_t *info, int sect) | |
| { | |
| return 0; | |
| } | |
| /* Write a word to flash */ | |
| static int write_word(flash_info_t *info, ulong dest, ulong word) | |
| { | |
| ulong flags; | |
| int rc; | |
| /* read flash to check if it is sufficiently erased */ | |
| if ((readl((void __iomem *)dest) & word) != word) { | |
| printf("Error, Flash not erased!\n"); | |
| return ERR_NOT_ERASED; | |
| } | |
| /* disable interrupts */ | |
| flags = disable_interrupts(); | |
| /* update destination page address (physical) */ | |
| writel(CPHYSADDR(dest), &nvm_regs_p->addr.raw); | |
| writel(word, &nvm_regs_p->data.raw); | |
| /* word write */ | |
| flash_initiate_operation(NVMOP_WORD_WRITE); | |
| /* wait for operation to complete */ | |
| rc = flash_wait_till_busy(__func__, CONFIG_SYS_FLASH_WRITE_TOUT); | |
| /* re-enable interrupts if necessary */ | |
| if (flags) | |
| enable_interrupts(); | |
| if (rc != ERR_OK) | |
| return rc; | |
| return flash_complete_operation(); | |
| } | |
| /* | |
| * Copy memory to flash, returns: | |
| * ERR_OK - OK | |
| * ERR_TIMOUT - write timeout | |
| * ERR_NOT_ERASED - Flash not erased | |
| */ | |
| int write_buff(flash_info_t *info, uchar *src, ulong addr, ulong cnt) | |
| { | |
| ulong dst, tmp_le, len = cnt; | |
| int i, l, rc; | |
| uchar *cp; | |
| /* get lower word aligned address */ | |
| dst = (addr & ~3); | |
| /* handle unaligned start bytes */ | |
| l = addr - dst; | |
| if (l != 0) { | |
| tmp_le = 0; | |
| for (i = 0, cp = (uchar *)dst; i < l; ++i, ++cp) | |
| tmp_le |= *cp << (i * 8); | |
| for (; (i < 4) && (cnt > 0); ++i, ++src, --cnt, ++cp) | |
| tmp_le |= *src << (i * 8); | |
| for (; (cnt == 0) && (i < 4); ++i, ++cp) | |
| tmp_le |= *cp << (i * 8); | |
| rc = write_word(info, dst, tmp_le); | |
| if (rc) | |
| goto out; | |
| dst += 4; | |
| } | |
| /* handle word aligned part */ | |
| while (cnt >= 4) { | |
| tmp_le = src[0] | src[1] << 8 | src[2] << 16 | src[3] << 24; | |
| rc = write_word(info, dst, tmp_le); | |
| if (rc) | |
| goto out; | |
| src += 4; | |
| dst += 4; | |
| cnt -= 4; | |
| } | |
| if (cnt == 0) { | |
| rc = ERR_OK; | |
| goto out; | |
| } | |
| /* handle unaligned tail bytes */ | |
| tmp_le = 0; | |
| for (i = 0, cp = (uchar *)dst; (i < 4) && (cnt > 0); ++i, ++cp) { | |
| tmp_le |= *src++ << (i * 8); | |
| --cnt; | |
| } | |
| for (; i < 4; ++i, ++cp) | |
| tmp_le |= *cp << (i * 8); | |
| rc = write_word(info, dst, tmp_le); | |
| out: | |
| /* | |
| * flash content updated by nvm controller but CPU cache might | |
| * have stale data, so invalidate dcache. | |
| */ | |
| invalidate_dcache_range(addr, addr + len); | |
| printf(" done\n"); | |
| return rc; | |
| } | |
| void flash_print_info(flash_info_t *info) | |
| { | |
| int i; | |
| if (info->flash_id == FLASH_UNKNOWN) { | |
| printf("missing or unknown FLASH type\n"); | |
| return; | |
| } | |
| switch (info->flash_id & FLASH_VENDMASK) { | |
| case FLASH_MAN_MCHP: | |
| printf("Microchip Technology "); | |
| break; | |
| default: | |
| printf("Unknown Vendor "); | |
| break; | |
| } | |
| switch (info->flash_id & FLASH_TYPEMASK) { | |
| case FLASH_MCHP100T: | |
| printf("Internal (8 Mbit, 64 x 16k)\n"); | |
| break; | |
| default: | |
| printf("Unknown Chip Type\n"); | |
| break; | |
| } | |
| printf(" Size: %ld MB in %d Sectors\n", | |
| info->size >> 20, info->sector_count); | |
| printf(" Sector Start Addresses:"); | |
| for (i = 0; i < info->sector_count; ++i) { | |
| if ((i % 5) == 0) | |
| printf("\n "); | |
| printf(" %08lX%s", info->start[i], | |
| info->protect[i] ? " (RO)" : " "); | |
| } | |
| printf("\n"); | |
| } | |
| unsigned long flash_init(void) | |
| { | |
| unsigned long size = 0; | |
| struct udevice *dev; | |
| int bank; | |
| /* probe every MTD device */ | |
| for (uclass_first_device(UCLASS_MTD, &dev); dev; | |
| uclass_next_device(&dev)) { | |
| /* nop */ | |
| } | |
| /* calc total flash size */ | |
| for (bank = 0; bank < CONFIG_SYS_MAX_FLASH_BANKS; ++bank) | |
| size += flash_info[bank].size; | |
| return size; | |
| } | |
| static void pic32_flash_bank_init(flash_info_t *info, | |
| ulong base, ulong size) | |
| { | |
| ulong sect_size; | |
| int sect; | |
| /* device & manufacturer code */ | |
| info->flash_id = FLASH_MAN_MCHP | FLASH_MCHP100T; | |
| info->sector_count = CONFIG_SYS_MAX_FLASH_SECT; | |
| info->size = size; | |
| /* update sector (i.e page) info */ | |
| sect_size = info->size / info->sector_count; | |
| for (sect = 0; sect < info->sector_count; sect++) { | |
| info->start[sect] = base; | |
| /* protect each sector by default */ | |
| info->protect[sect] = 1; | |
| base += sect_size; | |
| } | |
| } | |
| static int pic32_flash_probe(struct udevice *dev) | |
| { | |
| void *blob = (void *)gd->fdt_blob; | |
| int node = dev_of_offset(dev); | |
| const char *list, *end; | |
| const fdt32_t *cell; | |
| unsigned long addr, size; | |
| int parent, addrc, sizec; | |
| flash_info_t *info; | |
| int len, idx; | |
| /* | |
| * decode regs. there are multiple reg tuples, and they need to | |
| * match with reg-names. | |
| */ | |
| parent = fdt_parent_offset(blob, node); | |
| fdt_support_default_count_cells(blob, parent, &addrc, &sizec); | |
| list = fdt_getprop(blob, node, "reg-names", &len); | |
| if (!list) | |
| return -ENOENT; | |
| end = list + len; | |
| cell = fdt_getprop(blob, node, "reg", &len); | |
| if (!cell) | |
| return -ENOENT; | |
| for (idx = 0, info = &flash_info[0]; list < end;) { | |
| addr = fdt_translate_address((void *)blob, node, cell + idx); | |
| size = fdt_addr_to_cpu(cell[idx + addrc]); | |
| len = strlen(list); | |
| if (!strncmp(list, "nvm", len)) { | |
| /* NVM controller */ | |
| nvm_regs_p = ioremap(addr, size); | |
| } else if (!strncmp(list, "bank", 4)) { | |
| /* Flash bank: use kseg0 cached address */ | |
| pic32_flash_bank_init(info, CKSEG0ADDR(addr), size); | |
| info++; | |
| } | |
| idx += addrc + sizec; | |
| list += len + 1; | |
| } | |
| /* disable flash write/erase operations */ | |
| writel(NVM_WREN, &nvm_regs_p->ctrl.clr); | |
| #if (CONFIG_SYS_MONITOR_BASE >= CONFIG_SYS_FLASH_BASE) | |
| /* monitor protection ON by default */ | |
| flash_protect(FLAG_PROTECT_SET, | |
| CONFIG_SYS_MONITOR_BASE, | |
| CONFIG_SYS_MONITOR_BASE + monitor_flash_len - 1, | |
| &flash_info[0]); | |
| #endif | |
| #ifdef CONFIG_ENV_IS_IN_FLASH | |
| /* ENV protection ON by default */ | |
| flash_protect(FLAG_PROTECT_SET, | |
| CONFIG_ENV_ADDR, | |
| CONFIG_ENV_ADDR + CONFIG_ENV_SECT_SIZE - 1, | |
| &flash_info[0]); | |
| #endif | |
| return 0; | |
| } | |
| static const struct udevice_id pic32_flash_ids[] = { | |
| { .compatible = "microchip,pic32mzda-flash" }, | |
| {} | |
| }; | |
| U_BOOT_DRIVER(pic32_flash) = { | |
| .name = "pic32_flash", | |
| .id = UCLASS_MTD, | |
| .of_match = pic32_flash_ids, | |
| .probe = pic32_flash_probe, | |
| }; |