/* NOR Flash Driver for Intel 28F160C3 NOR flash */ /* (C) 2010 by Harald Welte * * 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. * */ #include #include #include #include #include #include #include /* XXX: strings must always be in ram */ #if 0 #define puts(...) #define printf(...) #endif /* global definitions */ #define CFI_FLASH_MAX_ERASE_REGIONS 4 /* structure of erase region descriptor */ struct cfi_region { uint16_t b_count; uint16_t b_size; } __attribute__ ((packed)); /* structure of cfi query response */ struct cfi_query { uint8_t qry[3]; uint16_t p_id; uint16_t p_adr; uint16_t a_id; uint16_t a_adr; uint8_t vcc_min; uint8_t vcc_max; uint8_t vpp_min; uint8_t vpp_max; uint8_t word_write_timeout_typ; uint8_t buf_write_timeout_typ; uint8_t block_erase_timeout_typ; uint8_t chip_erase_timeout_typ; uint8_t word_write_timeout_max; uint8_t buf_write_timeout_max; uint8_t block_erase_timeout_max; uint8_t chip_erase_timeout_max; uint8_t dev_size; uint16_t interface_desc; uint16_t max_buf_write_size; uint8_t num_erase_regions; struct cfi_region erase_regions[CFI_FLASH_MAX_ERASE_REGIONS]; } __attribute__ ((packed)); /* algorithm ids */ enum cfi_algo { CFI_ALGO_INTEL_3 = 0x03 }; /* various command bytes */ enum cfi_flash_cmd { CFI_CMD_RESET = 0xff, CFI_CMD_RESET_TO_READ_MODE = 0xF0, CFI_CMD_READ_ID = 0x90, CFI_CMD_CFI = 0x98, CFI_CMD_READ_STATUS = 0x70, CFI_CMD_CLEAR_STATUS = 0x50, CFI_CMD_WRITE = 0x40, CFI_CMD_BLOCK_ERASE = 0x20, CFI_CMD_ERASE_CONFIRM = 0xD0, CFI_CMD_PROTECT = 0x60, CFI_CMD_UNLOCK1 = 0xAA, CFI_CMD_UNLOCK2 = 0x55, }; /* protection commands */ enum flash_prot_cmd { CFI_PROT_LOCK = 0x01, CFI_PROT_UNLOCK = 0xD0, CFI_PROT_LOCKDOWN = 0x2F }; /* offsets from base */ enum flash_offset { CFI_OFFSET_MANUFACTURER_ID = 0x00, CFI_OFFSET_DEVICE_ID = 0x01, CFI_OFFSET_EXT_DEVICE_ID1 = 0x0E, CFI_OFFSET_EXT_DEVICE_ID2 = 0x0F, CFI_OFFSET_INTEL_PROTECTION = 0x81, CFI_OFFSET_CFI_RESP = 0x10 }; /* offsets from block base */ enum flash_block_offset { CFI_OFFSET_BLOCK_LOCKSTATE = 0x02 }; /* status masks */ enum flash_status { CFI_STATUS_READY = 0x80, CFI_STATUS_ERASE_SUSPENDED = 0x40, CFI_STATUS_ERASE_ERROR = 0x20, CFI_STATUS_PROGRAM_ERROR = 0x10, CFI_STATUS_VPP_LOW = 0x08, CFI_STATUS_PROGRAM_SUSPENDED = 0x04, CFI_STATUS_LOCKED_ERROR = 0x02, CFI_STATUS_RESERVED = 0x01 }; #define CFI_CMD_ADDR1 0xAAA #define CFI_CMD_ADDR2 0x555 __ramtext static inline void flash_write_cmd(const void *base_addr, uint16_t cmd) { writew(cmd, base_addr); } __ramtext static inline uint16_t flash_read16(const void *base_addr, uint32_t offset) { return readw(base_addr + (offset << 1)); } __ramtext static char flash_protected(uint32_t block_offset) { #ifdef CONFIG_FLASH_WRITE # ifdef CONFIG_FLASH_WRITE_LOADER return 0; # else return block_offset <= 0xFFFF; # endif #else return 1; #endif } __ramtext flash_lock_t flash_block_getlock(flash_t * flash, uint32_t block_offset) { const void *base_addr = flash->f_base; uint8_t lockstate; flash_write_cmd(base_addr, CFI_CMD_READ_ID); lockstate = flash_read16(base_addr, (block_offset >> 1) + CFI_OFFSET_BLOCK_LOCKSTATE); flash_write_cmd(base_addr, CFI_CMD_RESET); if (lockstate & 0x2) { return FLASH_LOCKED_DOWN; } else if (lockstate & 0x01) { return FLASH_LOCKED; } else { return FLASH_UNLOCKED; } } __ramtext int flash_block_unlock(flash_t * flash, uint32_t block_offset) { const void *base_addr = flash->f_base; if (block_offset >= flash->f_size) { return -EINVAL; } if (flash_protected(block_offset)) { return -EPERM; } printf("Unlocking block at 0x%08lx, meaning %p\n", block_offset, base_addr + block_offset); flash_write_cmd(base_addr, CFI_CMD_PROTECT); flash_write_cmd(base_addr + block_offset, CFI_PROT_UNLOCK); flash_write_cmd(base_addr, CFI_CMD_RESET); return 0; } __ramtext int flash_block_lock(flash_t * flash, uint32_t block_offset) { const void *base_addr = flash->f_base; if (block_offset >= flash->f_size) { return -EINVAL; } printf("Locking block at 0x%08lx\n", block_offset); flash_write_cmd(base_addr, CFI_CMD_PROTECT); flash_write_cmd(base_addr + block_offset, CFI_PROT_LOCK); flash_write_cmd(base_addr, CFI_CMD_RESET); return 0; } __ramtext int flash_block_lockdown(flash_t * flash, uint32_t block_offset) { const void *base_addr = flash->f_base; if (block_offset >= flash->f_size) { return -EINVAL; } printf("Locking down block at 0x%08lx\n", block_offset); flash_write_cmd(base_addr, CFI_CMD_PROTECT); flash_write_cmd(base_addr + block_offset, CFI_PROT_LOCKDOWN); flash_write_cmd(base_addr, CFI_CMD_RESET); return 0; } __ramtext int flash_block_erase(flash_t * flash, uint32_t block_offset) { const void *base_addr = flash->f_base; if (block_offset >= flash->f_size) { return -EINVAL; } if (flash_protected(block_offset)) { return -EPERM; } printf("Erasing block 0x%08lx...", block_offset); void *block_addr = ((uint8_t *) base_addr) + block_offset; flash_write_cmd(base_addr, CFI_CMD_CLEAR_STATUS); flash_write_cmd(block_addr, CFI_CMD_BLOCK_ERASE); flash_write_cmd(block_addr, CFI_CMD_ERASE_CONFIRM); flash_write_cmd(base_addr, CFI_CMD_READ_STATUS); uint16_t status; do { status = flash_read16(base_addr, 0); } while (!(status & CFI_STATUS_READY)); int res = 0; if (status & CFI_STATUS_ERASE_ERROR) { puts("error: "); if (status & CFI_STATUS_VPP_LOW) { puts("vpp insufficient\n"); res = -EFAULT; } else if (status & CFI_STATUS_LOCKED_ERROR) { puts("block is lock-protected\n"); res = -EPERM; } else { puts("unknown fault\n"); res = -EFAULT; } } else { puts("done\n"); } flash_write_cmd(base_addr, CFI_CMD_RESET); return res; } __ramtext int flash_program(flash_t * flash, uint32_t dst, void *src, uint32_t nbytes) { const void *base_addr = flash->f_base; int res = 0; uint32_t i; /* check destination bounds */ if (dst >= flash->f_size) { return -EINVAL; } if (dst + nbytes > flash->f_size) { return -EINVAL; } /* check alignments */ if (((uint32_t) src) % 2) { return -EINVAL; } if (dst % 2) { return -EINVAL; } if (nbytes % 2) { return -EINVAL; } /* check permissions */ if (flash_protected(dst)) { return -EPERM; } /* say something */ printf("Programming %lu bytes to 0x%08lx from 0x%p...", nbytes, dst, src); /* clear status register */ flash_write_cmd(base_addr, CFI_CMD_CLEAR_STATUS); /* write the words */ 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, CFI_CMD_WRITE); flash_write_cmd(dst_addr, data); flash_write_cmd(base_addr, CFI_CMD_READ_STATUS); uint16_t status; do { status = flash_read16(base_addr, 0); } while (!(status & CFI_STATUS_READY)); if (status & CFI_STATUS_PROGRAM_ERROR) { puts("error: "); if (status & CFI_STATUS_VPP_LOW) { puts("vpp insufficient"); res = -EFAULT; } else if (status & CFI_STATUS_LOCKED_ERROR) { puts("block is lock-protected"); res = -EPERM; } else { puts("unknown fault"); res = -EFAULT; } goto err_reset; } } flash_write_cmd(base_addr, CFI_CMD_RESET); /* verify the result */ 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"); res = -EFAULT; goto err; } } puts("done\n"); return res; err_reset: flash_write_cmd(base_addr, CFI_CMD_RESET); err: printf(" at offset 0x%lx\n", i); return res; } /* retrieve manufacturer and extended device id from id space */ __ramtext int flash_get_id(void *base_addr, uint16_t * manufacturer_id, uint16_t * device_id) { flash_write_cmd(base_addr, CFI_CMD_RESET_TO_READ_MODE); flash_write_cmd(base_addr + CFI_CMD_ADDR1, CFI_CMD_UNLOCK1); flash_write_cmd(base_addr + CFI_CMD_ADDR2, CFI_CMD_UNLOCK2); flash_write_cmd(base_addr + CFI_CMD_ADDR1, CFI_CMD_READ_ID); if (manufacturer_id) *manufacturer_id = flash_read16(base_addr, CFI_OFFSET_MANUFACTURER_ID); if (device_id) { device_id[0] = flash_read16(base_addr, CFI_OFFSET_DEVICE_ID); device_id[1] = flash_read16(base_addr, CFI_OFFSET_EXT_DEVICE_ID1); device_id[2] = flash_read16(base_addr, CFI_OFFSET_EXT_DEVICE_ID2); } flash_write_cmd(base_addr, CFI_CMD_RESET_TO_READ_MODE); return 0; } /* Internal: retrieve cfi query response data */ __ramtext static int get_query(void *base_addr, struct cfi_query *query) { int res = 0; unsigned int i; flash_write_cmd(base_addr, CFI_CMD_CFI); for (i = 0; i < sizeof(struct cfi_query); i++) { uint16_t byte = flash_read16(base_addr, CFI_OFFSET_CFI_RESP + i); *(((volatile unsigned char *)query) + i) = byte; } if (query->qry[0] != 'Q' || query->qry[1] != 'R' || query->qry[2] != 'Y') { res = -ENOENT; } flash_write_cmd(base_addr, CFI_CMD_RESET); return res; } #if 0 /* Internal: retrieve intel protection data */ __ramtext static int get_intel_protection(void *base_addr, uint16_t * lockp, uint8_t protp[8]) { int i; /* check args */ if (!lockp) { return -EINVAL; } if (!protp) { return -EINVAL; } /* enter read id mode */ flash_write_cmd(base_addr, CFI_CMD_READ_ID); /* get lock */ *lockp = flash_read16(base_addr, CFI_OFFSET_INTEL_PROTECTION); /* get data */ for (i = 0; i < 8; i++) { protp[i] = flash_read16(base_addr, CFI_OFFSET_INTEL_PROTECTION + 1 + i); } /* leave read id mode */ flash_write_cmd(base_addr, CFI_CMD_RESET); return 0; } static void dump_intel_protection(uint16_t lock, uint8_t data[8]) { printf (" protection lock 0x%4.4x data 0x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n", lock, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); } static void dump_query_algorithms(struct cfi_query *qry) { printf(" primary algorithm 0x%4.4x\n", qry->p_id); printf(" primary extended query 0x%4.4x\n", qry->p_adr); printf(" alternate algorithm 0x%4.4x\n", qry->a_id); printf(" alternate extended query 0x%4.4x\n", qry->a_adr); } static void dump_query_timing(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 typ %u ms\n", block_erase_typ); printf(" block erase max %u ms\n", block_erase_max); printf(" word program typ %u us\n", word_program_typ); printf(" word program max %u us\n", word_program_max); } void flash_dump_info(flash_t * flash) { int i; printf("flash at 0x%p of %d bytes with %d regions\n", flash->f_base, flash->f_size, flash->f_nregions); uint16_t m_id, d_id; if (get_id(flash->f_base, &m_id, &d_id)) { puts(" failed to get id\n"); } else { printf(" manufacturer 0x%4.4x device 0x%4.4x\n", m_id, d_id); } uint16_t plock; uint8_t pdata[8]; if (get_intel_protection(flash->f_base, &plock, pdata)) { puts(" failed to get protection data\n"); } else { dump_intel_protection(plock, pdata); } struct cfi_query qry; if (get_query(flash->f_base, &qry)) { puts(" failed to get cfi query response\n"); } else { dump_query_algorithms(&qry); dump_query_timing(&qry); } for (i = 0; i < flash->f_nregions; i++) { flash_region_t *fr = &flash->f_regions[i]; printf(" region %d: %d blocks of %d bytes at 0x%p\n", i, fr->fr_bnum, fr->fr_bsize, fr->fr_base); } } #endif __ramtext int flash_init(flash_t * flash, void *base_addr) { int res; unsigned u; uint16_t m_id, d_id[3]; uint32_t base; struct cfi_query qry; /* retrieve and check manufacturer and device id */ res = flash_get_id(base_addr, &m_id, d_id); if (res) { return res; } if (m_id != CFI_MANUF_INTEL && m_id != CFI_MANUF_ST) { return -ENOTSUP; } /* retrieve and check query response */ res = get_query(base_addr, &qry); if (res) { return res; } if (qry.p_id != CFI_ALGO_INTEL_3) { /* we only support algo 3 */ return -ENOTSUP; } if (qry.num_erase_regions > FLASH_MAX_REGIONS) { /* we have a hard limit on the number of regions */ return -ENOTSUP; } /* fill in basic information */ flash->f_base = base_addr; flash->f_size = 1 << qry.dev_size; /* determine number of erase regions */ flash->f_nregions = qry.num_erase_regions; /* compute actual erase region info from cfi junk */ base = 0; for (u = 0; u < flash->f_nregions; u++) { flash_region_t *fr = &flash->f_regions[u]; fr->fr_base = (void *)base; fr->fr_bnum = qry.erase_regions[u].b_count + 1; fr->fr_bsize = qry.erase_regions[u].b_size * 256; base += fr->fr_bnum * fr->fr_bsize; } return 0; }