/* * Copyright (C) 2015 * Cristian Birsan * Purna Chandra Mandal * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include 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; 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); of_bus_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, };