summaryrefslogtreecommitdiffstats
path: root/src/target/firmware/flash/cfi_flash.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/target/firmware/flash/cfi_flash.c')
-rw-r--r--src/target/firmware/flash/cfi_flash.c435
1 files changed, 435 insertions, 0 deletions
diff --git a/src/target/firmware/flash/cfi_flash.c b/src/target/firmware/flash/cfi_flash.c
new file mode 100644
index 00000000..faf44cbe
--- /dev/null
+++ b/src/target/firmware/flash/cfi_flash.c
@@ -0,0 +1,435 @@
+/* NOR Flash Driver for Intel 28F160C3 NOR flash */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <memory.h>
+#include <cfi_flash.h>
+
+/* XXX: memdump_range() */
+#include <calypso/misc.h>
+
+enum flash_cmd {
+ FLASH_CMD_RESET = 0xff,
+ FLASH_CMD_READ_ID = 0x90,
+ FLASH_CMD_CFI = 0x98,
+ FLASH_CMD_READ_STATUS = 0x70,
+ FLASH_CMD_CLEAR_STATUS = 0x50,
+ FLASH_CMD_WRITE = 0x40,
+ FLASH_CMD_BLOCK_ERASE = 0x20,
+ FLASH_CMD_ERASE_CONFIRM = 0xD0,
+ FLASH_CMD_PROTECT = 0x60,
+};
+
+enum flash_prot_cmd {
+ FLASH_PROT_LOCK = 0x01,
+ FLASH_PROT_UNLOCK = 0xD0,
+ FLASH_PROT_LOCKDOWN = 0x2F
+};
+
+enum flash_offset {
+ FLASH_OFFSET_MANUFACTURER_ID = 0x00,
+ FLASH_OFFSET_DEVICE_ID = 0x01,
+ FLASH_OFFSET_INTEL_PROTECTION = 0x81,
+ FLASH_OFFSET_CFI_RESP = 0x10
+};
+
+enum flash_block_offset {
+ FLASH_OFFSET_BLOCK_LOCKSTATE = 0x02
+};
+
+enum flash_status {
+ FLASH_STATUS_READY = 0x80,
+ FLASH_STATUS_ERASE_SUSPENDED = 0x40,
+ FLASH_STATUS_ERASE_ERROR = 0x20,
+ FLASH_STATUS_PROGRAM_ERROR = 0x10,
+ FLASH_STATUS_VPP_LOW = 0x08,
+ FLASH_STATUS_PROGRAM_SUSPENDED = 0x04,
+ FLASH_STATUS_LOCKED_ERROR = 0x02,
+ FLASH_STATUS_RESERVED = 0x01
+};
+
+static inline void flash_write_cmd(const void *base_addr, uint16_t cmd)
+{
+ writew(cmd, base_addr);
+}
+
+static inline uint16_t flash_read16(const void *base_addr, uint32_t offset)
+{
+ return readw(base_addr + (offset << 1));
+}
+
+static char flash_protected(uint32_t block_offset) {
+ return block_offset < 64*1024;
+}
+
+uint8_t flash_block_getlock(cfi_flash_t *flash, uint32_t block_offset) {
+ const void *base_addr = flash->f_base;
+ uint8_t lockstate;
+ flash_write_cmd(base_addr, FLASH_CMD_READ_ID);
+ lockstate = flash_read16(base_addr, block_offset + FLASH_OFFSET_BLOCK_LOCKSTATE);
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+ return lockstate;
+}
+
+void flash_block_unlock(cfi_flash_t *flash, uint32_t block_offset) {
+ const void *base_addr = flash->f_base;
+ printf("Unlocking block at 0x%08x\n", block_offset);
+
+ if(flash_protected(block_offset)) {
+ puts("error: block is soft-protected\n");
+ return;
+ }
+
+ flash_write_cmd(base_addr, FLASH_CMD_PROTECT);
+ flash_write_cmd(base_addr + block_offset, FLASH_PROT_UNLOCK);
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+void flash_block_lock(cfi_flash_t *flash, uint32_t block_offset) {
+ const void *base_addr = flash->f_base;
+ printf("Locking block at 0x%08x\n", block_offset);
+ flash_write_cmd(base_addr, FLASH_CMD_PROTECT);
+ flash_write_cmd(base_addr + block_offset, FLASH_PROT_LOCK);
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+void flash_block_lockdown(cfi_flash_t *flash, uint32_t block_offset) {
+ const void *base_addr = flash->f_base;
+ printf("Locking down block at 0x%08x\n", block_offset);
+ flash_write_cmd(base_addr, FLASH_CMD_PROTECT);
+ flash_write_cmd(base_addr + block_offset, FLASH_PROT_LOCKDOWN);
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+void flash_block_erase(cfi_flash_t *flash, uint32_t block_offset) {
+ const void *base_addr = flash->f_base;
+ printf("Erasing block 0x%08x...", block_offset);
+
+ if(flash_protected(block_offset)) {
+ puts("error: block is soft-protected\n");
+ return;
+ }
+
+ void *block_addr = ((uint8_t*)base_addr) + block_offset;
+
+ flash_write_cmd(base_addr, FLASH_CMD_CLEAR_STATUS);
+
+ flash_write_cmd(block_addr, FLASH_CMD_BLOCK_ERASE);
+ flash_write_cmd(block_addr, FLASH_CMD_ERASE_CONFIRM);
+
+ flash_write_cmd(base_addr, FLASH_CMD_READ_STATUS);
+ uint16_t status;
+ do {
+ status = flash_read16(base_addr, 0);
+ } while(!(status&FLASH_STATUS_READY));
+
+ if(status&FLASH_STATUS_ERASE_ERROR) {
+ puts("error: ");
+ if(status&FLASH_STATUS_VPP_LOW) {
+ puts("vpp insufficient\n");
+ }
+ if(status&FLASH_STATUS_LOCKED_ERROR) {
+ puts("block is lock-protected\n");
+ }
+ } else {
+ puts("done\n");
+ }
+
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+void flash_program(cfi_flash_t *flash, uint32_t dst, void *src, uint32_t nbytes) {
+ const void *base_addr = flash->f_base;
+ uint32_t i;
+
+ printf("Programming %u bytes to 0x%08x from 0x%p...", nbytes, dst, src);
+
+ if(dst%2) {
+ puts("error: unaligned destination\n");
+ return;
+ }
+
+ if(nbytes%2) {
+ puts("error: unaligned count\n");
+ return;
+ }
+
+ if(flash_protected(dst)) {
+ puts("error: block is soft-protected\n");
+ return;
+ }
+
+ flash_write_cmd(base_addr, FLASH_CMD_CLEAR_STATUS);
+
+ puts("writing...");
+ for(i = 0; i < nbytes; i += 2) {
+ uint16_t *src_addr = (uint16_t*)(src + i);
+ uint16_t *dst_addr = (uint16_t*)(base_addr + dst + i);
+
+ uint16_t data = *src_addr;
+
+ flash_write_cmd(dst_addr, FLASH_CMD_WRITE);
+ flash_write_cmd(dst_addr, data);
+
+ flash_write_cmd(base_addr, FLASH_CMD_READ_STATUS);
+ uint16_t status;
+ do {
+ status = flash_read16(base_addr, 0);
+ } while(!(status&FLASH_STATUS_READY));
+
+ if(status&FLASH_STATUS_PROGRAM_ERROR) {
+ puts("error: ");
+ if(status&FLASH_STATUS_VPP_LOW) {
+ puts("vpp insufficient");
+ }
+ if(status&FLASH_STATUS_LOCKED_ERROR) {
+ puts("block is lock-protected");
+ }
+ goto err_reset;
+ }
+ }
+
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+
+ puts("verifying...");
+ for(i = 0; i < nbytes; i += 2) {
+ uint16_t *src_addr = (uint16_t*)(src + i);
+ uint16_t *dst_addr = (uint16_t*)(base_addr + dst + i);
+ if(*src_addr != *dst_addr) {
+ puts("error: verification failed");
+ goto err;
+ }
+ }
+
+ puts("done\n");
+
+ return;
+
+ err_reset:
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+
+ err:
+ printf(" at offset 0x%x\n", i);
+}
+
+typedef void (*flash_block_cb_t)(cfi_flash_t *flash,
+ uint32_t block_offset,
+ uint32_t block_size);
+
+void flash_iterate_blocks(cfi_flash_t *flash, struct cfi_query *qry,
+ uint32_t start_offset, uint32_t end_offset,
+ flash_block_cb_t callback)
+{
+ int region, block;
+
+ uint32_t block_start = 0;
+ for(region = 0; region < qry->num_erase_regions; region++) {
+ uint16_t actual_count = qry->erase_regions[region].b_count + 1;
+ uint32_t actual_size = qry->erase_regions[region].b_size * 256;
+ for(block = 0; block < actual_count; block++) {
+ uint32_t block_end = block_start + actual_size;
+ if(block_start >= start_offset && block_end-1 <= end_offset) {
+ callback(flash, block_start, actual_size);
+ }
+ block_start = block_end;
+ }
+ }
+}
+
+static void get_id(void *base_addr, uint16_t *manufacturer_id, uint16_t *device_id) {
+ flash_write_cmd(base_addr, FLASH_CMD_READ_ID);
+
+ *manufacturer_id = flash_read16(base_addr, FLASH_OFFSET_MANUFACTURER_ID);
+ *device_id = flash_read16(base_addr, FLASH_OFFSET_DEVICE_ID);
+
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+static void get_query(void *base_addr, struct cfi_query *query) {
+ unsigned int i;
+
+ flash_write_cmd(base_addr, FLASH_CMD_CFI);
+
+ for(i = 0; i < sizeof(struct cfi_query); i++) {
+ uint16_t byte = flash_read16(base_addr, FLASH_OFFSET_CFI_RESP+i);
+ *(((unsigned char*)query)+i) = byte;
+ }
+
+ if(query->qry[0] != 'Q' || query->qry[1] != 'R' || query->qry[2] != 'Y') {
+ puts("Error: CFI query signature not found\n");
+ }
+
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+static void dump_query(void *base_addr, struct cfi_query *query) {
+ unsigned int i;
+
+ flash_write_cmd(base_addr, FLASH_CMD_CFI);
+
+ for(i = 0; i < sizeof(struct cfi_query); i++) {
+ uint8_t byte = *(((uint8_t*)query)+i);
+ printf("%04X: %02X\n", FLASH_OFFSET_CFI_RESP+i, byte);
+ }
+
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+static void dump_layout(void *base_addr, const struct cfi_query *qry) {
+ int region;
+
+ flash_write_cmd(base_addr, FLASH_CMD_READ_ID);
+ for(region = 0; region < qry->num_erase_regions; region++) {
+ uint16_t actual_count = qry->erase_regions[region].b_count + 1;
+ uint32_t actual_size = qry->erase_regions[region].b_size * 256;
+ printf("Region of 0x%04x times 0x%6x bytes\n", actual_count,
+ actual_size);
+ }
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+static void dump_locks(void *base_addr, const struct cfi_query *qry) {
+ int region, block;
+
+ uint32_t block_addr = 0;
+ flash_write_cmd(base_addr, FLASH_CMD_READ_ID);
+ for(region = 0; region < qry->num_erase_regions; region++) {
+ uint16_t actual_count = qry->erase_regions[region].b_count + 1;
+ uint32_t actual_size = qry->erase_regions[region].b_size * 256;
+ for(block = 0; block < actual_count; block++) {
+ uint8_t lock = flash_read16(base_addr, block_addr+2);
+ printf("Block 0x%08x lock 0x%02x\n", block_addr*2, lock);
+ block_addr += actual_size / 2;
+ }
+ }
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+static void dump_protection(void *base_addr) {
+ flash_write_cmd(base_addr, FLASH_CMD_READ_ID);
+
+ uint16_t lock = flash_read16(base_addr, FLASH_OFFSET_INTEL_PROTECTION);
+ printf("Protection Lock: 0x%04x\n", lock);
+
+ puts("Protection Data: ");
+ int i;
+ for(i = 0; i < 8; i++) {
+ printf("%04x", flash_read16(base_addr, FLASH_OFFSET_INTEL_PROTECTION + 1 + i));
+ }
+ putchar('\n');
+
+ flash_write_cmd(base_addr, FLASH_CMD_RESET);
+}
+
+static void dump_timing(void *base_addr, struct cfi_query *qry) {
+ uint32_t block_erase_typ = 1<<qry->block_erase_timeout_typ;
+ uint32_t block_erase_max = (1<<qry->block_erase_timeout_max) * block_erase_typ;
+ uint32_t word_program_typ = 1<<qry->word_write_timeout_typ;
+ uint32_t word_program_max = (1<<qry->word_write_timeout_max) * word_program_typ;
+ printf("Block Erase Typical: %u ms\n", block_erase_typ);
+ printf("Block Erase Maximum: %u ms\n", block_erase_max);
+ printf("Word Program Typical: %u us\n", word_program_typ);
+ printf("Word Program Maximum: %u us\n", word_program_max);
+}
+
+static void dump_algorithms(void *base_addr, struct cfi_query *qry) {
+ printf("Primary Algorithm ID: %04x\n", qry->p_id);
+ printf("Primary Extended Query: %04x\n", qry->p_adr);
+
+ printf("Alternate Algorithm ID: %04x\n", qry->a_id);
+ printf("Alternate Extended Query: %04x\n", qry->a_adr);
+}
+
+void
+lockdown_block_cb(cfi_flash_t *flash,
+ uint32_t block_offset,
+ uint32_t block_size)
+{
+ flash_block_lockdown(flash, block_offset);
+}
+
+void
+print_block_cb(cfi_flash_t *flash,
+ uint32_t block_offset,
+ uint32_t block_size)
+{
+ printf("%08x size %08x\n", block_offset, block_size);
+}
+
+void flash_dump_info(cfi_flash_t *flash) {
+ void *base_addr = flash->f_base;
+ struct cfi_query *qry = &flash->f_query;
+
+ printf("Flash Manufacturer ID: %04x\n", flash->f_manuf_id);
+ printf("Flash Device ID: %04x\n", flash->f_dev_id);
+
+ printf("Flash Size: 0x%08x bytes\n", flash->f_size);
+
+ dump_algorithms(base_addr, qry);
+
+ dump_timing(base_addr, qry);
+
+ dump_protection(base_addr);
+
+ dump_layout(base_addr, qry);
+
+ dump_locks(base_addr, qry);
+}
+
+void flash_init(cfi_flash_t *flash, void *base_addr) {
+ printf("Initializing CFI flash at 0x%p\n", base_addr);
+
+ flash->f_base = base_addr;
+
+ get_id(base_addr, &flash->f_manuf_id, &flash->f_dev_id);
+
+ get_query(base_addr, &flash->f_query);
+
+ flash->f_size = 1<<flash->f_query.dev_size;
+}
+
+void flash_test() {
+ /* block iterator test */
+#if 0
+ flash_iterate_blocks(flash, qry, 0x0000, 0xFFFF, &lockdown_block_cb);
+#endif
+
+ /* programming test */
+#if 0
+ static uint8_t magic[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xDE, 0xAD, 0xBE, 0xEF};
+
+ memdump_range(&magic, sizeof(magic));
+
+#if 0
+#define ADDR 0x001E0000
+ flash_block_unlock(flash, ADDR);
+ memdump_range(ADDR, 16);
+ flash_block_erase(flash, ADDR);
+ memdump_range(ADDR, 16);
+ flash_program(flash, ADDR, &magic, sizeof(magic));
+ memdump_range(ADDR, 16);
+#undef ADDR
+#endif
+
+#endif
+}