Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 1 | /* |
| 2 | * (C) Copyright 2010 |
| 3 | * Reinhard Meyer, EMK Elektronik, reinhard.meyer@emk-elektronik.de |
| 4 | * |
Wolfgang Denk | d79de1d | 2013-07-08 09:37:19 +0200 | [diff] [blame] | 5 | * SPDX-License-Identifier: GPL-2.0+ |
Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 6 | */ |
| 7 | |
| 8 | /* |
| 9 | * this driver supports the enhanced embedded flash in the Atmel |
| 10 | * AT91SAM9XE devices with the following geometry: |
| 11 | * |
| 12 | * AT91SAM9XE128: 1 plane of 8 regions of 32 pages (total 256 pages) |
| 13 | * AT91SAM9XE256: 1 plane of 16 regions of 32 pages (total 512 pages) |
| 14 | * AT91SAM9XE512: 1 plane of 32 regions of 32 pages (total 1024 pages) |
| 15 | * (the exact geometry is read from the flash at runtime, so any |
| 16 | * future devices should already be covered) |
| 17 | * |
| 18 | * Regions can be write/erase protected. |
| 19 | * Whole (!) pages can be individually written with erase on the fly. |
| 20 | * Writing partial pages will corrupt the rest of the page. |
| 21 | * |
| 22 | * The flash is presented to u-boot with each region being a sector, |
| 23 | * having the following effects: |
| 24 | * Each sector can be hardware protected (protect on/off). |
| 25 | * Each page in a sector can be rewritten anytime. |
| 26 | * Since pages are erased when written, the "erase" does nothing. |
| 27 | * The first "CONFIG_EFLASH_PROTSECTORS" cannot be unprotected |
| 28 | * by u-Boot commands. |
| 29 | * |
| 30 | * Note: Redundant environment will not work in this flash since |
Robert P. J. Day | 832d36e | 2013-09-16 07:15:45 -0400 | [diff] [blame] | 31 | * it does use partial page writes. Make sure the environment spans |
Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 32 | * whole pages! |
| 33 | */ |
| 34 | |
| 35 | /* |
| 36 | * optional TODOs (nice to have features): |
| 37 | * |
| 38 | * make the driver coexist with other NOR flash drivers |
| 39 | * (use an index into flash_info[], requires work |
| 40 | * in those other drivers, too) |
| 41 | * Make the erase command fill the sectors with 0xff |
| 42 | * (if the flashes grow larger in the future and |
| 43 | * someone puts a jffs2 into them) |
| 44 | * do a read-modify-write for partially programmed pages |
| 45 | */ |
| 46 | #include <common.h> |
Reinhard Meyer | b06208c | 2010-11-07 13:26:14 +0100 | [diff] [blame] | 47 | #include <asm/io.h> |
Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 48 | #include <asm/arch/hardware.h> |
Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 49 | #include <asm/arch/at91_common.h> |
| 50 | #include <asm/arch/at91_eefc.h> |
| 51 | #include <asm/arch/at91_dbu.h> |
| 52 | |
| 53 | /* checks to detect configuration errors */ |
| 54 | #if CONFIG_SYS_MAX_FLASH_BANKS!=1 |
| 55 | #error eflash: this driver can only handle 1 bank |
| 56 | #endif |
| 57 | |
| 58 | /* global structure */ |
| 59 | flash_info_t flash_info[CONFIG_SYS_MAX_FLASH_BANKS]; |
| 60 | static u32 pagesize; |
| 61 | |
| 62 | unsigned long flash_init (void) |
| 63 | { |
Reinhard Meyer | e260d0b | 2010-11-03 15:39:55 +0100 | [diff] [blame] | 64 | at91_eefc_t *eefc = (at91_eefc_t *) ATMEL_BASE_EEFC; |
| 65 | at91_dbu_t *dbu = (at91_dbu_t *) ATMEL_BASE_DBGU; |
Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 66 | u32 id, size, nplanes, planesize, nlocks; |
| 67 | u32 addr, i, tmp=0; |
| 68 | |
| 69 | debug("eflash: init\n"); |
| 70 | |
| 71 | flash_info[0].flash_id = FLASH_UNKNOWN; |
| 72 | |
| 73 | /* check if its an AT91ARM9XE SoC */ |
| 74 | if ((readl(&dbu->cidr) & AT91_DBU_CID_ARCH_MASK) != AT91_DBU_CID_ARCH_9XExx) { |
| 75 | puts("eflash: not an AT91SAM9XE\n"); |
| 76 | return 0; |
| 77 | } |
| 78 | |
| 79 | /* now query the eflash for its structure */ |
| 80 | writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_GETD, &eefc->fcr); |
| 81 | while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| 82 | ; |
| 83 | id = readl(&eefc->frr); /* word 0 */ |
| 84 | size = readl(&eefc->frr); /* word 1 */ |
| 85 | pagesize = readl(&eefc->frr); /* word 2 */ |
| 86 | nplanes = readl(&eefc->frr); /* word 3 */ |
| 87 | planesize = readl(&eefc->frr); /* word 4 */ |
| 88 | debug("id=%08x size=%u pagesize=%u planes=%u planesize=%u\n", |
| 89 | id, size, pagesize, nplanes, planesize); |
| 90 | for (i=1; i<nplanes; i++) { |
| 91 | tmp = readl(&eefc->frr); /* words 5..4+nplanes-1 */ |
| 92 | }; |
| 93 | nlocks = readl(&eefc->frr); /* word 4+nplanes */ |
| 94 | debug("nlocks=%u\n", nlocks); |
| 95 | /* since we are going to use the lock regions as sectors, check count */ |
| 96 | if (nlocks > CONFIG_SYS_MAX_FLASH_SECT) { |
| 97 | printf("eflash: number of lock regions(%u) "\ |
| 98 | "> CONFIG_SYS_MAX_FLASH_SECT. reducing...\n", |
| 99 | nlocks); |
| 100 | nlocks = CONFIG_SYS_MAX_FLASH_SECT; |
| 101 | } |
| 102 | flash_info[0].size = size; |
| 103 | flash_info[0].sector_count = nlocks; |
| 104 | flash_info[0].flash_id = id; |
| 105 | |
Reinhard Meyer | e260d0b | 2010-11-03 15:39:55 +0100 | [diff] [blame] | 106 | addr = ATMEL_BASE_FLASH; |
Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 107 | for (i=0; i<nlocks; i++) { |
| 108 | tmp = readl(&eefc->frr); /* words 4+nplanes+1.. */ |
| 109 | flash_info[0].start[i] = addr; |
| 110 | flash_info[0].protect[i] = 0; |
| 111 | addr += tmp; |
| 112 | }; |
| 113 | |
| 114 | /* now read the protection information for all regions */ |
| 115 | writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_GLB, &eefc->fcr); |
| 116 | while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| 117 | ; |
| 118 | for (i=0; i<flash_info[0].sector_count; i++) { |
| 119 | if (i%32 == 0) |
| 120 | tmp = readl(&eefc->frr); |
| 121 | flash_info[0].protect[i] = (tmp >> (i%32)) & 1; |
| 122 | #if defined(CONFIG_EFLASH_PROTSECTORS) |
| 123 | if (i < CONFIG_EFLASH_PROTSECTORS) |
| 124 | flash_info[0].protect[i] = 1; |
| 125 | #endif |
| 126 | } |
| 127 | |
| 128 | return size; |
| 129 | } |
| 130 | |
| 131 | void flash_print_info (flash_info_t *info) |
| 132 | { |
| 133 | int i; |
| 134 | |
| 135 | puts("AT91SAM9XE embedded flash\n Size: "); |
| 136 | print_size(info->size, " in "); |
| 137 | printf("%d Sectors\n", info->sector_count); |
| 138 | |
| 139 | printf(" Sector Start Addresses:"); |
| 140 | for (i=0; i<info->sector_count; ++i) { |
| 141 | if ((i % 5) == 0) |
| 142 | printf("\n "); |
| 143 | printf(" %08lX%s", |
| 144 | info->start[i], |
| 145 | info->protect[i] ? " (RO)" : " " |
| 146 | ); |
| 147 | } |
| 148 | printf ("\n"); |
| 149 | return; |
| 150 | } |
| 151 | |
| 152 | int flash_real_protect (flash_info_t *info, long sector, int prot) |
| 153 | { |
Reinhard Meyer | e260d0b | 2010-11-03 15:39:55 +0100 | [diff] [blame] | 154 | at91_eefc_t *eefc = (at91_eefc_t *) ATMEL_BASE_EEFC; |
| 155 | u32 pagenum = (info->start[sector]-ATMEL_BASE_FLASH)/pagesize; |
Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 156 | u32 i, tmp=0; |
| 157 | |
| 158 | debug("protect sector=%ld prot=%d\n", sector, prot); |
| 159 | |
| 160 | #if defined(CONFIG_EFLASH_PROTSECTORS) |
| 161 | if (sector < CONFIG_EFLASH_PROTSECTORS) { |
| 162 | if (!prot) { |
| 163 | printf("eflash: sector %lu cannot be unprotected\n", |
| 164 | sector); |
| 165 | } |
| 166 | return 1; /* return anyway, caller does not care for result */ |
| 167 | } |
| 168 | #endif |
| 169 | if (prot) { |
| 170 | writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_SLB | |
| 171 | (pagenum << AT91_EEFC_FCR_FARG_SHIFT), &eefc->fcr); |
| 172 | } else { |
| 173 | writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_CLB | |
| 174 | (pagenum << AT91_EEFC_FCR_FARG_SHIFT), &eefc->fcr); |
| 175 | } |
| 176 | while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| 177 | ; |
| 178 | /* now re-read the protection information for all regions */ |
| 179 | writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_GLB, &eefc->fcr); |
| 180 | while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| 181 | ; |
| 182 | for (i=0; i<info->sector_count; i++) { |
| 183 | if (i%32 == 0) |
| 184 | tmp = readl(&eefc->frr); |
| 185 | info->protect[i] = (tmp >> (i%32)) & 1; |
| 186 | } |
| 187 | return 0; |
| 188 | } |
| 189 | |
| 190 | static u32 erase_write_page (u32 pagenum) |
| 191 | { |
Reinhard Meyer | e260d0b | 2010-11-03 15:39:55 +0100 | [diff] [blame] | 192 | at91_eefc_t *eefc = (at91_eefc_t *) ATMEL_BASE_EEFC; |
Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 193 | |
| 194 | debug("erase+write page=%u\n", pagenum); |
| 195 | |
| 196 | /* give erase and write page command */ |
| 197 | writel(AT91_EEFC_FCR_KEY | AT91_EEFC_FCR_FCMD_EWP | |
| 198 | (pagenum << AT91_EEFC_FCR_FARG_SHIFT), &eefc->fcr); |
| 199 | while ((readl(&eefc->fsr) & AT91_EEFC_FSR_FRDY) == 0) |
| 200 | ; |
| 201 | /* return status */ |
| 202 | return readl(&eefc->fsr) |
| 203 | & (AT91_EEFC_FSR_FCMDE | AT91_EEFC_FSR_FLOCKE); |
| 204 | } |
| 205 | |
| 206 | int flash_erase (flash_info_t *info, int s_first, int s_last) |
| 207 | { |
| 208 | debug("erase first=%d last=%d\n", s_first, s_last); |
| 209 | puts("this flash does not need and support erasing!\n"); |
| 210 | return 0; |
| 211 | } |
| 212 | |
| 213 | /* |
| 214 | * Copy memory to flash, returns: |
| 215 | * 0 - OK |
| 216 | * 1 - write timeout |
| 217 | */ |
| 218 | |
| 219 | int write_buff (flash_info_t *info, uchar *src, ulong addr, ulong cnt) |
| 220 | { |
| 221 | u32 pagenum; |
| 222 | u32 *src32, *dst32; |
| 223 | u32 i; |
| 224 | |
| 225 | debug("write src=%08lx addr=%08lx cnt=%lx\n", |
| 226 | (ulong)src, addr, cnt); |
| 227 | |
| 228 | /* REQUIRE addr to be on a page start, abort if not */ |
| 229 | if (addr % pagesize) { |
| 230 | printf ("eflash: start %08lx is not on page start\n"\ |
| 231 | " write aborted\n", addr); |
| 232 | return 1; |
| 233 | } |
| 234 | |
| 235 | /* now start copying data */ |
Reinhard Meyer | e260d0b | 2010-11-03 15:39:55 +0100 | [diff] [blame] | 236 | pagenum = (addr-ATMEL_BASE_FLASH)/pagesize; |
Reinhard Meyer | 8ec18f4 | 2010-07-27 15:18:38 +0200 | [diff] [blame] | 237 | src32 = (u32 *) src; |
| 238 | dst32 = (u32 *) addr; |
| 239 | while (cnt > 0) { |
| 240 | i = pagesize / 4; |
| 241 | /* fill page buffer */ |
| 242 | while (i--) |
| 243 | *dst32++ = *src32++; |
| 244 | /* write page */ |
| 245 | if (erase_write_page(pagenum)) |
| 246 | return 1; |
| 247 | pagenum++; |
| 248 | if (cnt > pagesize) |
| 249 | cnt -= pagesize; |
| 250 | else |
| 251 | cnt = 0; |
| 252 | } |
| 253 | return 0; |
| 254 | } |