diff options
-rw-r--r-- | drivers/mtd/Kconfig | 7 | ||||
-rw-r--r-- | drivers/mtd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mtd/pic32_flash.c | 444 |
3 files changed, 452 insertions, 0 deletions
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index c58841e..390e9e4 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -28,6 +28,13 @@ config ALTERA_QSPI NOR flash to parallel flash interface. Please find details on the "Embedded Peripherals IP User Guide" of Altera. +config FLASH_PIC32 + bool "Microchip PIC32 Flash driver" + depends on MACH_PIC32 && MTD + help + This enables access to Microchip PIC32 internal non-CFI flash + chips through PIC32 Non-Volatile-Memory Controller. + endmenu source "drivers/mtd/nand/Kconfig" diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 703700a..bd680a7 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -19,5 +19,6 @@ obj-$(CONFIG_HAS_DATAFLASH) += dataflash.o obj-$(CONFIG_FTSMC020) += ftsmc020.o obj-$(CONFIG_FLASH_CFI_LEGACY) += jedec_flash.o obj-$(CONFIG_MW_EEPROM) += mw_eeprom.o +obj-$(CONFIG_FLASH_PIC32) += pic32_flash.o obj-$(CONFIG_ST_SMI) += st_smi.o obj-$(CONFIG_STM32_FLASH) += stm32_flash.o diff --git a/drivers/mtd/pic32_flash.c b/drivers/mtd/pic32_flash.c new file mode 100644 index 0000000..9166fcd --- /dev/null +++ b/drivers/mtd/pic32_flash.c @@ -0,0 +1,444 @@ +/* + * 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; + 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, +}; |