| /* |
| * board/eva/flash.c |
| * |
| * (C) Copyright 2002 |
| * Sangmoon Kim, Etin Systems, dogoil@etinsys.com. |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| #include <common.h> |
| #include <asm/processor.h> |
| #include <asm/pci_io.h> |
| #include <mpc824x.h> |
| |
| int (*do_flash_erase)(flash_info_t*, uint32_t, uint32_t); |
| int (*write_dword)(flash_info_t*, ulong, uint64_t); |
| |
| typedef uint64_t cfi_word; |
| |
| #define cfi_read(flash, addr) *((volatile cfi_word*)(flash->start[0] + addr)) |
| |
| #define cfi_write(flash, val, addr) \ |
| move64((cfi_word*)&val, \ |
| (cfi_word*)(flash->start[0] + addr)) |
| |
| #define CMD(x) ((((cfi_word)x)<<48)|(((cfi_word)x)<<32)|(((cfi_word)x)<<16)|(((cfi_word)x))) |
| |
| static void write32(unsigned long addr, uint32_t value) |
| { |
| *(volatile uint32_t*)(addr) = value; |
| asm volatile("sync"); |
| } |
| |
| static uint32_t read32(unsigned long addr) |
| { |
| uint32_t value; |
| value = *(volatile uint32_t*)addr; |
| asm volatile("sync"); |
| return value; |
| } |
| |
| static cfi_word cfi_cmd(flash_info_t *flash, uint8_t cmd, uint32_t addr) |
| { |
| uint32_t base = flash->start[0]; |
| uint32_t val=(cmd << 16) | cmd; |
| addr <<= 3; |
| write32(base + addr, val); |
| return addr; |
| } |
| |
| static uint16_t cfi_read_query(flash_info_t *flash, uint32_t addr) |
| { |
| uint32_t base = flash->start[0]; |
| addr <<= 3; |
| return (uint16_t)read32(base + addr); |
| } |
| |
| flash_info_t flash_info[CFG_MAX_FLASH_BANKS]; /* info for FLASH chips */ |
| |
| static void move64(uint64_t *src, uint64_t *dest) |
| { |
| asm volatile("lfd 0, 0(3)\n\t" /* fpr0 = *scr */ |
| "stfd 0, 0(4)" /* *dest = fpr0 */ |
| : : : "fr0" ); /* Clobbers fr0 */ |
| return; |
| } |
| |
| static int cfi_write_dword(flash_info_t *flash, ulong dest, cfi_word data) |
| { |
| unsigned long start; |
| cfi_word status = 0; |
| |
| status = cfi_read(flash, dest); |
| data &= status; |
| |
| cfi_cmd(flash, 0x40, 0); |
| cfi_write(flash, data, dest); |
| |
| udelay(10); |
| start = get_timer (0); |
| for(;;) { |
| status = cfi_read(flash, dest); |
| status &= CMD(0x80); |
| if(status == CMD(0x80)) |
| break; |
| if (get_timer(start) > CFG_FLASH_WRITE_TOUT) { |
| cfi_cmd(flash, 0xff, 0); |
| return 1; |
| } |
| udelay(1); |
| } |
| cfi_cmd(flash, 0xff, 0); |
| |
| return 0; |
| } |
| |
| static int jedec_write_dword (flash_info_t *flash, ulong dest, cfi_word data) |
| { |
| ulong start; |
| cfi_word status = 0; |
| |
| status = cfi_read(flash, dest); |
| if(status != CMD(0xffff)) return 2; |
| |
| cfi_cmd(flash, 0xaa, 0x555); |
| cfi_cmd(flash, 0x55, 0x2aa); |
| cfi_cmd(flash, 0xa0, 0x555); |
| |
| cfi_write(flash, data, dest); |
| |
| udelay(10); |
| start = get_timer (0); |
| status = ~data; |
| while(status != data) { |
| if (get_timer(start) > CFG_FLASH_WRITE_TOUT) |
| return 1; |
| status = cfi_read(flash, dest); |
| udelay(1); |
| } |
| return 0; |
| } |
| |
| static __inline__ unsigned long get_msr(void) |
| { |
| unsigned long msr; |
| __asm__ __volatile__ ("mfmsr %0" : "=r" (msr) :); |
| return msr; |
| } |
| |
| static __inline__ void set_msr(unsigned long msr) |
| { |
| __asm__ __volatile__ ("mtmsr %0" : : "r" (msr)); |
| } |
| |
| int write_buff (flash_info_t *flash, uchar *src, ulong addr, ulong cnt) |
| { |
| ulong wp; |
| int i, s, l, rc; |
| cfi_word data; |
| uint8_t *t = (uint8_t*)&data; |
| unsigned long base = flash->start[0]; |
| uint32_t msr; |
| |
| if (flash->flash_id == FLASH_UNKNOWN) |
| return 4; |
| |
| if (cnt == 0) |
| return 0; |
| |
| addr -= base; |
| |
| msr = get_msr(); |
| set_msr(msr|MSR_FP); |
| |
| wp = (addr & ~7); /* get lower word aligned address */ |
| |
| if((addr-wp) != 0) { |
| data = cfi_read(flash, wp); |
| s = addr & 7; |
| l = ( cnt < (8-s) ) ? cnt : (8-s); |
| for(i = 0; i < l; i++) |
| t[s+i] = *src++; |
| if ((rc = write_dword(flash, wp, data)) != 0) |
| goto DONE; |
| wp += 8; |
| cnt -= l; |
| } |
| |
| while (cnt >= 8) { |
| for (i = 0; i < 8; i++) |
| t[i] = *src++; |
| if ((rc = write_dword(flash, wp, data)) != 0) |
| goto DONE; |
| wp += 8; |
| cnt -= 8; |
| } |
| |
| if (cnt == 0) { |
| rc = 0; |
| goto DONE; |
| } |
| |
| data = cfi_read(flash, wp); |
| for(i = 0; i < cnt; i++) |
| t[i] = *src++; |
| rc = write_dword(flash, wp, data); |
| DONE: |
| set_msr(msr); |
| return rc; |
| } |
| |
| static int cfi_erase_oneblock(flash_info_t *flash, uint32_t sect) |
| { |
| int sa; |
| int flag; |
| ulong start, last, now; |
| cfi_word status; |
| |
| flag = disable_interrupts(); |
| |
| sa = (flash->start[sect] - flash->start[0]); |
| write32(flash->start[sect], 0x00200020); |
| write32(flash->start[sect], 0x00d000d0); |
| |
| if (flag) |
| enable_interrupts(); |
| |
| udelay(1000); |
| start = get_timer (0); |
| last = start; |
| |
| for (;;) { |
| status = cfi_read(flash, sa); |
| status &= CMD(0x80); |
| if (status == CMD(0x80)) |
| break; |
| if ((now = get_timer(start)) > CFG_FLASH_ERASE_TOUT) { |
| cfi_cmd(flash, 0xff, 0); |
| printf ("Timeout\n"); |
| return ERR_TIMOUT; |
| } |
| |
| if ((now - last) > 1000) { |
| serial_putc ('.'); |
| last = now; |
| } |
| udelay(10); |
| } |
| cfi_cmd(flash, 0xff, 0); |
| return ERR_OK; |
| } |
| |
| static int cfi_erase(flash_info_t *flash, uint32_t s_first, uint32_t s_last) |
| { |
| int sect; |
| int rc = ERR_OK; |
| |
| for (sect = s_first; sect <= s_last; sect++) { |
| if (flash->protect[sect] == 0) { |
| rc = cfi_erase_oneblock(flash, sect); |
| if (rc != ERR_OK) break; |
| } |
| } |
| printf (" done\n"); |
| return rc; |
| } |
| |
| static int jedec_erase(flash_info_t *flash, uint32_t s_first, uint32_t s_last) |
| { |
| int sect; |
| cfi_word status; |
| int sa = -1; |
| int flag; |
| ulong start, last, now; |
| |
| flag = disable_interrupts(); |
| |
| cfi_cmd(flash, 0xaa, 0x555); |
| cfi_cmd(flash, 0x55, 0x2aa); |
| cfi_cmd(flash, 0x80, 0x555); |
| cfi_cmd(flash, 0xaa, 0x555); |
| cfi_cmd(flash, 0x55, 0x2aa); |
| for ( sect = s_first; sect <= s_last; sect++) { |
| if (flash->protect[sect] == 0) { |
| sa = flash->start[sect] - flash->start[0]; |
| write32(flash->start[sect], 0x00300030); |
| } |
| } |
| if (flag) |
| enable_interrupts(); |
| |
| if (sa < 0) |
| goto DONE; |
| |
| udelay (1000); |
| start = get_timer (0); |
| last = start; |
| for(;;) { |
| status = cfi_read(flash, sa); |
| if (status == CMD(0xffff)) |
| break; |
| |
| if ((now = get_timer(start)) > CFG_FLASH_ERASE_TOUT) { |
| printf ("Timeout\n"); |
| return ERR_TIMOUT; |
| } |
| |
| if ((now - last) > 1000) { |
| serial_putc ('.'); |
| last = now; |
| } |
| udelay(10); |
| } |
| DONE: |
| cfi_cmd(flash, 0xf0, 0); |
| |
| printf (" done\n"); |
| |
| return ERR_OK; |
| } |
| |
| int flash_erase (flash_info_t *flash, int s_first, int s_last) |
| { |
| int sect; |
| int prot; |
| |
| if ((s_first < 0) || (s_first > s_last)) { |
| if (flash->flash_id == FLASH_UNKNOWN) |
| printf ("- missing\n"); |
| else |
| printf ("- no sectors to erase\n"); |
| return ERR_NOT_ERASED; |
| } |
| if (flash->flash_id == FLASH_UNKNOWN) { |
| printf ("Can't erase unknown flash type - aborted\n"); |
| return ERR_NOT_ERASED; |
| } |
| |
| prot = 0; |
| for (sect = s_first; sect <= s_last; sect++) |
| if (flash->protect[sect]) prot++; |
| |
| if (prot) |
| printf ("- Warning: %d protected sectors will not be erased!\n", |
| prot); |
| else |
| printf ("\n"); |
| |
| return do_flash_erase(flash, s_first, s_last); |
| } |
| |
| struct jedec_flash_info { |
| const uint16_t mfr_id; |
| const uint16_t dev_id; |
| const char *name; |
| const int DevSize; |
| const int InterfaceDesc; |
| const int NumEraseRegions; |
| const ulong regions[4]; |
| }; |
| |
| #define ERASEINFO(size,blocks) (size<<8)|(blocks-1) |
| |
| #define SIZE_1MiB 20 |
| #define SIZE_2MiB 21 |
| #define SIZE_4MiB 22 |
| |
| static const struct jedec_flash_info jedec_table[] = { |
| { |
| mfr_id: (uint16_t)AMD_MANUFACT, |
| dev_id: (uint16_t)AMD_ID_LV800T, |
| name: "AMD AM29LV800T", |
| DevSize: SIZE_1MiB, |
| NumEraseRegions: 4, |
| regions: {ERASEINFO(0x10000,15), |
| ERASEINFO(0x08000,1), |
| ERASEINFO(0x02000,2), |
| ERASEINFO(0x04000,1) |
| } |
| }, { |
| mfr_id: (uint16_t)AMD_MANUFACT, |
| dev_id: (uint16_t)AMD_ID_LV800B, |
| name: "AMD AM29LV800B", |
| DevSize: SIZE_1MiB, |
| NumEraseRegions: 4, |
| regions: {ERASEINFO(0x10000,15), |
| ERASEINFO(0x08000,1), |
| ERASEINFO(0x02000,2), |
| ERASEINFO(0x04000,1) |
| } |
| }, { |
| mfr_id: (uint16_t)AMD_MANUFACT, |
| dev_id: (uint16_t)AMD_ID_LV160T, |
| name: "AMD AM29LV160T", |
| DevSize: SIZE_2MiB, |
| NumEraseRegions: 4, |
| regions: {ERASEINFO(0x10000,31), |
| ERASEINFO(0x08000,1), |
| ERASEINFO(0x02000,2), |
| ERASEINFO(0x04000,1) |
| } |
| }, { |
| mfr_id: (uint16_t)AMD_MANUFACT, |
| dev_id: (uint16_t)AMD_ID_LV160B, |
| name: "AMD AM29LV160B", |
| DevSize: SIZE_2MiB, |
| NumEraseRegions: 4, |
| regions: {ERASEINFO(0x04000,1), |
| ERASEINFO(0x02000,2), |
| ERASEINFO(0x08000,1), |
| ERASEINFO(0x10000,31) |
| } |
| }, { |
| mfr_id: (uint16_t)AMD_MANUFACT, |
| dev_id: (uint16_t)AMD_ID_LV320T, |
| name: "AMD AM29LV320T", |
| DevSize: SIZE_4MiB, |
| NumEraseRegions: 2, |
| regions: {ERASEINFO(0x10000,63), |
| ERASEINFO(0x02000,8) |
| } |
| |
| }, { |
| mfr_id: (uint16_t)AMD_MANUFACT, |
| dev_id: (uint16_t)AMD_ID_LV320B, |
| name: "AMD AM29LV320B", |
| DevSize: SIZE_4MiB, |
| NumEraseRegions: 2, |
| regions: {ERASEINFO(0x02000,8), |
| ERASEINFO(0x10000,63) |
| } |
| } |
| }; |
| |
| static ulong cfi_init(uint32_t base, flash_info_t *flash) |
| { |
| int sector; |
| int block; |
| int block_count; |
| int offset = 0; |
| int reverse = 0; |
| int primary; |
| int mfr_id; |
| int dev_id; |
| |
| flash->start[0] = base; |
| cfi_cmd(flash, 0xF0, 0); |
| cfi_cmd(flash, 0x98, 0); |
| if ( !( cfi_read_query(flash, 0x10) == 'Q' && |
| cfi_read_query(flash, 0x11) == 'R' && |
| cfi_read_query(flash, 0x12) == 'Y' )) { |
| cfi_cmd(flash, 0xff, 0); |
| return 0; |
| } |
| |
| flash->size = 1 << cfi_read_query(flash, 0x27); |
| flash->size *= 4; |
| block_count = cfi_read_query(flash, 0x2c); |
| primary = cfi_read_query(flash, 0x15); |
| if ( cfi_read_query(flash, primary + 4) == 0x30) |
| reverse = (cfi_read_query(flash, 0x1) & 0x01); |
| else |
| reverse = (cfi_read_query(flash, primary+15) == 3); |
| |
| flash->sector_count = 0; |
| |
| for ( block = reverse ? block_count - 1 : 0; |
| reverse ? block >= 0 : block < block_count; |
| reverse ? block-- : block ++) { |
| int sector_size = |
| (cfi_read_query(flash, 0x2d + block*4+2) | |
| (cfi_read_query(flash, 0x2d + block*4+3) << 8)) << 8; |
| int sector_count = |
| (cfi_read_query(flash, 0x2d + block*4+0) | |
| (cfi_read_query(flash, 0x2d + block*4+1) << 8)) + 1; |
| for(sector = 0; sector < sector_count; sector++) { |
| flash->start[flash->sector_count++] = base + offset; |
| offset += sector_size * 4; |
| } |
| } |
| mfr_id = cfi_read_query(flash, 0x00); |
| dev_id = cfi_read_query(flash, 0x01); |
| |
| cfi_cmd(flash, 0xff, 0); |
| |
| flash->flash_id = (mfr_id << 16) | dev_id; |
| |
| for (sector = 0; sector < flash->sector_count; sector++) { |
| write32(flash->start[sector], 0x00600060); |
| write32(flash->start[sector], 0x00d000d0); |
| } |
| cfi_cmd(flash, 0xff, 0); |
| |
| for (sector = 0; sector < flash->sector_count; sector++) |
| flash->protect[sector] = 0; |
| |
| do_flash_erase = cfi_erase; |
| write_dword = cfi_write_dword; |
| |
| return flash->size; |
| } |
| |
| static ulong jedec_init(unsigned long base, flash_info_t *flash) |
| { |
| int i; |
| int block, block_count; |
| int sector, offset; |
| int mfr_id, dev_id; |
| flash->start[0] = base; |
| cfi_cmd(flash, 0xF0, 0x000); |
| cfi_cmd(flash, 0xAA, 0x555); |
| cfi_cmd(flash, 0x55, 0x2AA); |
| cfi_cmd(flash, 0x90, 0x555); |
| mfr_id = cfi_read_query(flash, 0x000); |
| dev_id = cfi_read_query(flash, 0x0001); |
| cfi_cmd(flash, 0xf0, 0x000); |
| |
| for(i=0; i<sizeof(jedec_table)/sizeof(struct jedec_flash_info); i++) { |
| if((jedec_table[i].mfr_id == mfr_id) && |
| (jedec_table[i].dev_id == dev_id)) { |
| |
| flash->flash_id = (mfr_id << 16) | dev_id; |
| flash->size = 1 << jedec_table[0].DevSize; |
| flash->size *= 4; |
| block_count = jedec_table[i].NumEraseRegions; |
| offset = 0; |
| flash->sector_count = 0; |
| for (block = 0; block < block_count; block++) { |
| int sector_size = jedec_table[i].regions[block]; |
| int sector_count = (sector_size & 0xff) + 1; |
| sector_size >>= 8; |
| for (sector=0; sector<sector_count; sector++) { |
| flash->start[flash->sector_count++] = |
| base + offset; |
| offset += sector_size * 4; |
| } |
| } |
| break; |
| } |
| } |
| |
| for (sector = 0; sector < flash->sector_count; sector++) |
| flash->protect[sector] = 0; |
| |
| do_flash_erase = jedec_erase; |
| write_dword = jedec_write_dword; |
| |
| return flash->size; |
| } |
| |
| inline void mtibat1u(unsigned int x) |
| { |
| __asm__ __volatile__ ("mtspr 530, %0" :: "r" (x)); |
| } |
| |
| inline void mtibat1l(unsigned int x) |
| { |
| __asm__ __volatile__ ("mtspr 531, %0" :: "r" (x)); |
| } |
| |
| inline void mtdbat1u(unsigned int x) |
| { |
| __asm__ __volatile__ ("mtspr 538, %0" :: "r" (x)); |
| } |
| |
| inline void mtdbat1l(unsigned int x) |
| { |
| __asm__ __volatile__ ("mtspr 539, %0" :: "r" (x)); |
| } |
| |
| unsigned long flash_init (void) |
| { |
| unsigned long size = 0; |
| int i; |
| unsigned int msr; |
| |
| /* BAT1 */ |
| CONFIG_WRITE_WORD(ERCR3, 0x0C00000C); |
| CONFIG_WRITE_WORD(ERCR4, 0x0800000C); |
| msr = get_msr(); |
| set_msr(msr & ~(MSR_IR | MSR_DR)); |
| mtibat1l(0x70000000 | BATL_PP_10 | BATL_CACHEINHIBIT); |
| mtibat1u(0x70000000 | BATU_BL_256M | BATU_VS | BATU_VP); |
| mtdbat1l(0x70000000 | BATL_PP_10 | BATL_CACHEINHIBIT); |
| mtdbat1u(0x70000000 | BATU_BL_256M | BATU_VS | BATU_VP); |
| set_msr(msr); |
| |
| for (i = 0; i < CFG_MAX_FLASH_BANKS; i++) |
| flash_info[i].flash_id = FLASH_UNKNOWN; |
| size = cfi_init(FLASH_BASE0_PRELIM, &flash_info[0]); |
| if (!size) |
| size = jedec_init(FLASH_BASE0_PRELIM, &flash_info[0]); |
| |
| if (flash_info[0].flash_id == FLASH_UNKNOWN) |
| printf ("# Unknown FLASH on Bank 1 - Size = 0x%08lx = %ld MB\n", |
| size, size<<20); |
| |
| return size; |
| } |
| |
| void flash_print_info (flash_info_t *flash) |
| { |
| int i; |
| int k; |
| int size; |
| int erased; |
| volatile unsigned long *p; |
| |
| if (flash->flash_id == FLASH_UNKNOWN) { |
| printf ("missing or unknown FLASH type\n"); |
| flash_init(); |
| } |
| |
| if (flash->flash_id == FLASH_UNKNOWN) { |
| printf ("missing or unknown FLASH type\n"); |
| return; |
| } |
| |
| switch (((flash->flash_id) >> 16) & 0xff) { |
| case 0x01: |
| printf ("AMD "); |
| break; |
| case 0x04: |
| printf("FUJITSU "); |
| break; |
| case 0x20: |
| printf("STM "); |
| break; |
| case 0xBF: |
| printf("SST "); |
| break; |
| case 0x89: |
| case 0xB0: |
| printf("INTEL "); |
| break; |
| default: |
| printf ("Unknown Vendor "); |
| break; |
| } |
| |
| switch ((flash->flash_id) & 0xffff) { |
| case (uint16_t)AMD_ID_LV800T: |
| printf ("AM29LV800T\n"); |
| break; |
| case (uint16_t)AMD_ID_LV800B: |
| printf ("AM29LV800B\n"); |
| break; |
| case (uint16_t)AMD_ID_LV160T: |
| printf ("AM29LV160T\n"); |
| break; |
| case (uint16_t)AMD_ID_LV160B: |
| printf ("AM29LV160B\n"); |
| break; |
| case (uint16_t)AMD_ID_LV320T: |
| printf ("AM29LV320T\n"); |
| break; |
| case (uint16_t)AMD_ID_LV320B: |
| printf ("AM29LV320B\n"); |
| break; |
| case (uint16_t)INTEL_ID_28F800C3T: |
| printf ("28F800C3T\n"); |
| break; |
| case (uint16_t)INTEL_ID_28F800C3B: |
| printf ("28F800C3B\n"); |
| break; |
| case (uint16_t)INTEL_ID_28F160C3T: |
| printf ("28F160C3T\n"); |
| break; |
| case (uint16_t)INTEL_ID_28F160C3B: |
| printf ("28F160C3B\n"); |
| break; |
| case (uint16_t)INTEL_ID_28F320C3T: |
| printf ("28F320C3T\n"); |
| break; |
| case (uint16_t)INTEL_ID_28F320C3B: |
| printf ("28F320C3B\n"); |
| break; |
| case (uint16_t)INTEL_ID_28F640C3T: |
| printf ("28F640C3T\n"); |
| break; |
| case (uint16_t)INTEL_ID_28F640C3B: |
| printf ("28F640C3B\n"); |
| break; |
| default: |
| printf ("Unknown Chip Type\n"); |
| break; |
| } |
| |
| if (flash->size >= (1 << 20)) { |
| printf (" Size: %ld MB in %d Sectors\n", |
| flash->size >> 20, flash->sector_count); |
| } else { |
| printf (" Size: %ld kB in %d Sectors\n", |
| flash->size >> 10, flash->sector_count); |
| } |
| |
| printf (" Sector Start Addresses:"); |
| for (i = 0; i < flash->sector_count; ++i) { |
| /* Check if whole sector is erased*/ |
| if (i != (flash->sector_count-1)) |
| size = flash->start[i+1] - flash->start[i]; |
| else |
| size = flash->start[0] + flash->size - flash->start[i]; |
| |
| erased = 1; |
| p = (volatile unsigned long *)flash->start[i]; |
| size = size >> 2; /* divide by 4 for longword access */ |
| for (k=0; k<size; k++) { |
| if (*p++ != 0xffffffff) { |
| erased = 0; |
| break; |
| } |
| } |
| |
| if ((i % 5) == 0) |
| printf ("\n "); |
| |
| printf (" %08lX%s%s", |
| flash->start[i], |
| erased ? " E" : " ", |
| flash->protect[i] ? "RO " : " "); |
| } |
| printf ("\n"); |
| } |