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