| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2015 |
| * Cristian Birsan <cristian.birsan@microchip.com> |
| * Purna Chandra Mandal <purna.mandal@microchip.com> |
| */ |
| |
| #include <common.h> |
| #include <cpu_func.h> |
| #include <dm.h> |
| #include <fdt_support.h> |
| #include <flash.h> |
| #include <init.h> |
| #include <irq_func.h> |
| #include <asm/global_data.h> |
| #include <linux/bitops.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_le32(&nvm_regs_p->ctrl.raw, |
| NVM_WR, false, timeout, false); |
| |
| return ret ? ERR_TIMEOUT : 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_TIMEOUT - 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_TIMEOUT - 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, |
| }; |