| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * (C) Copyright 2010 |
| * Reinhard Meyer, EMK Elektronik, reinhard.meyer@emk-elektronik.de |
| */ |
| |
| /* |
| * this driver supports the enhanced embedded flash in the Atmel |
| * AT91SAM9XE devices with the following geometry: |
| * |
| * AT91SAM9XE128: 1 plane of 8 regions of 32 pages (total 256 pages) |
| * AT91SAM9XE256: 1 plane of 16 regions of 32 pages (total 512 pages) |
| * AT91SAM9XE512: 1 plane of 32 regions of 32 pages (total 1024 pages) |
| * (the exact geometry is read from the flash at runtime, so any |
| * future devices should already be covered) |
| * |
| * Regions can be write/erase protected. |
| * Whole (!) pages can be individually written with erase on the fly. |
| * Writing partial pages will corrupt the rest of the page. |
| * |
| * The flash is presented to u-boot with each region being a sector, |
| * having the following effects: |
| * Each sector can be hardware protected (protect on/off). |
| * Each page in a sector can be rewritten anytime. |
| * Since pages are erased when written, the "erase" does nothing. |
| * The first "CONFIG_EFLASH_PROTSECTORS" cannot be unprotected |
| * by u-Boot commands. |
| * |
| * Note: Redundant environment will not work in this flash since |
| * it does use partial page writes. Make sure the environment spans |
| * whole pages! |
| */ |
| |
| /* |
| * optional TODOs (nice to have features): |
| * |
| * make the driver coexist with other NOR flash drivers |
| * (use an index into flash_info[], requires work |
| * in those other drivers, too) |
| * Make the erase command fill the sectors with 0xff |
| * (if the flashes grow larger in the future and |
| * someone puts a jffs2 into them) |
| * do a read-modify-write for partially programmed pages |
| */ |
| #include <common.h> |
| #include <display_options.h> |
| #include <flash.h> |
| #include <log.h> |
| #include <asm/io.h> |
| #include <asm/arch/hardware.h> |
| #include <asm/arch/at91_common.h> |
| #include <asm/arch/at91_eefc.h> |
| #include <asm/arch/at91_dbu.h> |
| |
| /* checks to detect configuration errors */ |
| #if CONFIG_SYS_MAX_FLASH_BANKS!=1 |
| #error eflash: this driver can only handle 1 bank |
| #endif |
| |
| /* global structure */ |
| flash_info_t flash_info[CONFIG_SYS_MAX_FLASH_BANKS]; |
| static u32 pagesize; |
| |
| unsigned long flash_init(void) |
| { |
| at91_eefc_t *eefc = (at91_eefc_t *) ATMEL_BASE_EEFC; |
| at91_dbu_t *dbu = (at91_dbu_t *) ATMEL_BASE_DBGU; |
| u32 id, size, nplanes, planesize, nlocks; |
| u32 addr, i, tmp=0; |
| |
| debug("eflash: init\n"); |
| |
| flash_info[0].flash_id = FLASH_UNKNOWN; |
| |
| /* check if its an AT91ARM9XE SoC */ |
| if ((readl(&dbu->cidr) & AT91_DBU_CID_ARCH_MASK) != AT91_DBU_CID_ARCH_9XExx) { |
| puts("eflash: not an AT91SAM9XE\n"); |
| return 0; |
| } |
| |
| /* now query the eflash for its structure */ |
| writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_GETD, &eefc->fcr); |
| while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| ; |
| id = readl(&eefc->frr); /* word 0 */ |
| size = readl(&eefc->frr); /* word 1 */ |
| pagesize = readl(&eefc->frr); /* word 2 */ |
| nplanes = readl(&eefc->frr); /* word 3 */ |
| planesize = readl(&eefc->frr); /* word 4 */ |
| debug("id=%08x size=%u pagesize=%u planes=%u planesize=%u\n", |
| id, size, pagesize, nplanes, planesize); |
| for (i=1; i<nplanes; i++) { |
| tmp = readl(&eefc->frr); /* words 5..4+nplanes-1 */ |
| }; |
| nlocks = readl(&eefc->frr); /* word 4+nplanes */ |
| debug("nlocks=%u\n", nlocks); |
| /* since we are going to use the lock regions as sectors, check count */ |
| if (nlocks > CONFIG_SYS_MAX_FLASH_SECT) { |
| printf("eflash: number of lock regions(%u) "\ |
| "> CONFIG_SYS_MAX_FLASH_SECT. reducing...\n", |
| nlocks); |
| nlocks = CONFIG_SYS_MAX_FLASH_SECT; |
| } |
| flash_info[0].size = size; |
| flash_info[0].sector_count = nlocks; |
| flash_info[0].flash_id = id; |
| |
| addr = ATMEL_BASE_FLASH; |
| for (i=0; i<nlocks; i++) { |
| tmp = readl(&eefc->frr); /* words 4+nplanes+1.. */ |
| flash_info[0].start[i] = addr; |
| flash_info[0].protect[i] = 0; |
| addr += tmp; |
| }; |
| |
| /* now read the protection information for all regions */ |
| writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_GLB, &eefc->fcr); |
| while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| ; |
| for (i=0; i<flash_info[0].sector_count; i++) { |
| if (i%32 == 0) |
| tmp = readl(&eefc->frr); |
| flash_info[0].protect[i] = (tmp >> (i%32)) & 1; |
| #if CONFIG_VAL(EFLASH_PROTSECTORS) |
| if (i < CONFIG_EFLASH_PROTSECTORS) |
| flash_info[0].protect[i] = 1; |
| #endif |
| } |
| |
| return size; |
| } |
| |
| void flash_print_info(flash_info_t *info) |
| { |
| int i; |
| |
| puts("AT91SAM9XE embedded flash\n Size: "); |
| print_size(info->size, " in "); |
| printf("%d Sectors\n", 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"); |
| return; |
| } |
| |
| int flash_real_protect (flash_info_t *info, long sector, int prot) |
| { |
| at91_eefc_t *eefc = (at91_eefc_t *) ATMEL_BASE_EEFC; |
| u32 pagenum = (info->start[sector]-ATMEL_BASE_FLASH)/pagesize; |
| u32 i, tmp=0; |
| |
| debug("protect sector=%ld prot=%d\n", sector, prot); |
| |
| #if CONFIG_VAL(EFLASH_PROTSECTORS) |
| if (sector < CONFIG_EFLASH_PROTSECTORS) { |
| if (!prot) { |
| printf("eflash: sector %lu cannot be unprotected\n", |
| sector); |
| } |
| return 1; /* return anyway, caller does not care for result */ |
| } |
| #endif |
| if (prot) { |
| writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_SLB | |
| (pagenum << AT91_EEFC_FCR_FARG_SHIFT), &eefc->fcr); |
| } else { |
| writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_CLB | |
| (pagenum << AT91_EEFC_FCR_FARG_SHIFT), &eefc->fcr); |
| } |
| while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| ; |
| /* now re-read the protection information for all regions */ |
| writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_GLB, &eefc->fcr); |
| while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| ; |
| for (i=0; i<info->sector_count; i++) { |
| if (i%32 == 0) |
| tmp = readl(&eefc->frr); |
| info->protect[i] = (tmp >> (i%32)) & 1; |
| } |
| return 0; |
| } |
| |
| static u32 erase_write_page (u32 pagenum) |
| { |
| at91_eefc_t *eefc = (at91_eefc_t *) ATMEL_BASE_EEFC; |
| |
| debug("erase+write page=%u\n", pagenum); |
| |
| /* give erase and write page command */ |
| writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_EWP | |
| (pagenum << AT91_EEFC_FCR_FARG_SHIFT), &eefc->fcr); |
| while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| ; |
| /* return status */ |
| return readl(&eefc->fsr) |
| & (AT91_EEFC_FSR_FCMDE | AT91_EEFC_FSR_FLOCKE); |
| } |
| |
| int flash_erase(flash_info_t *info, int s_first, int s_last) |
| { |
| debug("erase first=%d last=%d\n", s_first, s_last); |
| puts("this flash does not need and support erasing!\n"); |
| return 0; |
| } |
| |
| /* |
| * Copy memory to flash, returns: |
| * 0 - OK |
| * 1 - write timeout |
| */ |
| |
| int write_buff(flash_info_t *info, uchar *src, ulong addr, ulong cnt) |
| { |
| u32 pagenum; |
| u32 *src32, *dst32; |
| u32 i; |
| |
| debug("write src=%08lx addr=%08lx cnt=%lx\n", |
| (ulong)src, addr, cnt); |
| |
| /* REQUIRE addr to be on a page start, abort if not */ |
| if (addr % pagesize) { |
| printf ("eflash: start %08lx is not on page start\n"\ |
| " write aborted\n", addr); |
| return 1; |
| } |
| |
| /* now start copying data */ |
| pagenum = (addr-ATMEL_BASE_FLASH)/pagesize; |
| src32 = (u32 *) src; |
| dst32 = (u32 *) addr; |
| while (cnt > 0) { |
| i = pagesize / 4; |
| /* fill page buffer */ |
| while (i--) |
| *dst32++ = *src32++; |
| /* write page */ |
| if (erase_write_page(pagenum)) |
| return 1; |
| pagenum++; |
| if (cnt > pagesize) |
| cnt -= pagesize; |
| else |
| cnt = 0; |
| } |
| return 0; |
| } |