/* * flashutl.c - Flash Read/write/Erase routines * * Copyright 2007, Broadcom Corporation * All Rights Reserved. * * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. * * $Id: flashutl.c,v 1.1.1.1 2008/07/21 09:20:53 james26_jang Exp $ */ #include #include #define DECLARE_FLASHES #include #include #include #include #include #include #include #define DPRINT(x) #define ERR2 0x30 /* Mask for err UNUSED */ #define DONE 0x80 /* Mask for done */ #define WBUFSIZE 32 /* Write Buffer size */ #define FLASH_TRIES 4000000 /* retry count */ #define CMD_ADDR ((unsigned long)0xFFFFFFFF) /* 'which' param for block() */ #define BLOCK_BASE 0 /* Base of block */ #define BLOCK_LIM 1 /* Limit of block */ #define FLASH_ADDR(off) ((unsigned long)flashutl_base + (off)) /* Local vars */ static sb_t *sbh = NULL; static chipcregs_t *cc = NULL; /* Global vars */ uint8 *flashutl_base = NULL; flash_desc_t *flashutl_desc = NULL; flash_cmds_t *flashutl_cmd = NULL; uint8 flashutl_wsz = sizeof(uint16); static void scmd(uint16 cmd, unsigned long off); static void cmd(uint16 cmd, unsigned long off); static void flash_reset(void); static int flash_poll(unsigned long off, uint16 data); static unsigned long block(unsigned long addr, int which); static int flash_eraseblk(unsigned long off); static int flash_write(unsigned long off, uint8 *src, uint nbytes); static uint16 INLINE flash_readword(unsigned long addr); static void INLINE flash_writeword(unsigned long addr, uint16 data); int sysFlashErase(uint off, unsigned int numbytes); /* Read the flash ID and set the globals */ int sysFlashInit(char *flash_str) { osl_t *osh; uint32 fltype = PFLASH; uint16 flash_vendid = 0; uint16 flash_devid = 0; int idx; struct sflash *sflash; /* * Check for serial flash. */ sbh = sb_kattach(SB_OSH); ASSERT(sbh); osh = sb_osh(sbh); flashutl_base = (uint8*)OSL_UNCACHED(SB_FLASH1); flashutl_wsz = sizeof(uint16); cc = (chipcregs_t *)sb_setcore(sbh, SB_CC, 0); if (cc) { flashutl_base = (uint8*)OSL_UNCACHED(SB_FLASH2); flashutl_wsz = (R_REG(osh, &cc->flash_config) & CC_CFG_DS) ? sizeof(uint16) : sizeof(uint8); /* Select SFLASH ? */ fltype = R_REG(osh, &cc->capabilities) & CC_CAP_FLASH_MASK; if (fltype == SFLASH_ST || fltype == SFLASH_AT) { sflash = sflash_init(sbh, cc); flashutl_cmd = &sflash_cmd_t; flashutl_desc = &sflash_desc; flashutl_desc->size = sflash->size; if (flash_str) sprintf(flash_str, "SFLASH %d kB", sflash->size/1024); return (0); } } ASSERT(flashutl_wsz == sizeof(uint8) || flashutl_wsz == sizeof(uint16)); /* * Parallel flash support * Some flashes have different unlock addresses, try each it turn */ for (idx = 0; fltype == PFLASH && idx < ARRAYSIZE(flash_cmds); idx ++) { flashutl_cmd = &flash_cmds[idx]; if (flashutl_cmd->type == OLD) continue; if (flashutl_cmd->read_id) cmd(flashutl_cmd->read_id, CMD_ADDR); #ifdef MIPSEB flash_vendid = flash_readword(FLASH_ADDR(2)); flash_devid = flash_readword(FLASH_ADDR(0)); #else flash_vendid = flash_readword(FLASH_ADDR(0)); flash_devid = flash_readword(FLASH_ADDR(2)); #endif /* MIPSEB */ /* Funky AMD, uses 3 byte device ID so use first byte (4th addr) to * identify it is a 3-byte ID and use the next two bytes (5th & 6th addr) * to form a word for unique identification of format xxyy, where * xx = 5th addr and yy = 6th addr */ if ((flash_vendid == 1) && (flash_devid == 0x227e)) { /* Get real devid */ uint16 flash_devid_5th; #ifdef MIPSEB flash_devid_5th = flash_readword(FLASH_ADDR(0x1e)) << 8; flash_devid = (flash_readword(FLASH_ADDR(0x1c)) & 0xff) | flash_devid_5th; #else flash_devid_5th = flash_readword(FLASH_ADDR(0x1c)) << 8; flash_devid = (flash_readword(FLASH_ADDR(0x1e)) & 0xff) | flash_devid_5th; #endif /* MIPSEB */ } flashutl_desc = flashes; while (flashutl_desc->mfgid != 0 && !(flashutl_desc->mfgid == flash_vendid && flashutl_desc->devid == flash_devid)) { flashutl_desc++; } if (flashutl_desc->mfgid != 0) break; } if (flashutl_desc->mfgid == 0) { flashutl_desc = NULL; flashutl_cmd = NULL; } else { flashutl_cmd = flash_cmds; while (flashutl_cmd->type != 0 && flashutl_cmd->type != flashutl_desc->type) flashutl_cmd++; if (flashutl_cmd->type == 0) flashutl_cmd = NULL; } if (flashutl_cmd != NULL) { flash_reset(); } if (flashutl_desc == NULL) { if (flash_str) sprintf(flash_str, "UNKNOWN 0x%x 0x%x", flash_vendid, flash_devid); DPRINT(("Flash type UNKNOWN\n")); return 1; } if (flash_str) strcpy(flash_str, flashutl_desc->desc); DPRINT(("Flash type \"%s\"\n", flashutl_desc->desc)); return 0; } static int flash_eraseblk(unsigned long addr) { unsigned long a; uint16 st; a = (unsigned long)addr; if (a >= flashutl_desc->size) return 1; a = block(a, BLOCK_BASE); /* Ensure blocks are unlocked (for intel chips) */ if (flashutl_cmd->type == BSC) { scmd((unsigned char)INTEL_UNLOCK1, a); scmd((unsigned char)INTEL_UNLOCK2, a); } if (flashutl_cmd->pre_erase) cmd(flashutl_cmd->pre_erase, CMD_ADDR); if (flashutl_cmd->erase_block) cmd(flashutl_cmd->erase_block, a); if (flashutl_cmd->confirm) scmd(flashutl_cmd->confirm, a); if (flashutl_wsz == sizeof(uint8)) st = flash_poll(a, 0xff); else st = flash_poll(a, 0xffff); flash_reset(); if (st) { DPRINT(("Erase of block 0x%08lx-0x%08lx failed\n", a, block((unsigned long)addr, BLOCK_LIM))); return st; } DPRINT(("Erase of block 0x%08lx-0x%08lx done\n", a, block((unsigned long)addr, BLOCK_LIM))); return 0; } static int flash_write(unsigned long off, uint8 *src, uint nbytes) { uint8 *dest; uint16 st, data; uint i, len; ASSERT(flashutl_desc != NULL); if (off >= flashutl_desc->size) return 1; ASSERT(!(off & (flashutl_wsz - 1))); dest = (uint8*)FLASH_ADDR(off); st = 0; while (nbytes) { if ((flashutl_desc->type == SCS) && flashutl_cmd->write_buf && ((off & (WBUFSIZE - 1)) == 0)) { /* issue write command */ if (flashutl_cmd->write_buf) cmd(flashutl_cmd->write_buf, off); if ((st = flash_poll(off, DONE))) continue; len = MIN(nbytes, WBUFSIZE); #ifndef MIPSEB /* write (length - 1) */ cmd(len / sizeof(uint16) - 1, off); /* write data */ for (i = 0; i < len; i += sizeof(uint16), dest += sizeof(uint16), src += sizeof(uint16)) *(uint16 *)dest = *(uint16 *)src; #else /* * BCM4710 endianness is word consistent but * byte/short scrambled. This write buffer * mechanism appears to be sensitive to the * order of the addresses hence we need to * unscramble them. We may also need to pad * the source with two bytes of 0xffff in case * an odd number of shorts are presented. */ /* write (padded length - 1) */ cmd((ROUNDUP(len, sizeof(uint32)) / sizeof(uint16)) - 1, off); /* write data (plus pad if necessary) */ for (i = 0; i < ROUNDUP(len, sizeof(uint32)); i += sizeof(uint32), dest += sizeof(uint32), src += sizeof(uint32)) { *((uint16 *)dest + 1) = ((i + sizeof(uint16)) < len) ? *((uint16 *)src + 1) : 0xffff; *(uint16 *)dest = *(uint16 *)src; } #endif /* MIPSEB */ /* write confirm */ if (flashutl_cmd->confirm) cmd(flashutl_cmd->confirm, off); if ((st = flash_poll(off, DONE))) break; } else { /* issue write command */ if (flashutl_cmd->write_word) cmd(flashutl_cmd->write_word, CMD_ADDR); /* write data */ data = flash_readword((unsigned long)src); flash_writeword((unsigned long)dest, data); /* poll for done */ if ((st = flash_poll(off, data))) break; len = MIN(nbytes, flashutl_wsz); dest += len; src += len; } nbytes -= len; off += len; } flash_reset(); return st; } static uint16 INLINE flash_readword(unsigned long addr) { if (flashutl_wsz == sizeof(uint8)) return *(uint8*)addr; else return *(uint16*)addr; } static void INLINE flash_writeword(unsigned long addr, uint16 data) { if (flashutl_wsz == sizeof(uint8)) *(uint8*)addr = (uint8)data; else *(uint16*)addr = data; } /* Writes a single command to the flash. */ static void scmd(uint16 cmd, unsigned long off) { /* cmd |= cmd << 8; */ flash_writeword(FLASH_ADDR(off), cmd); } /* Writes a command to flash, performing an unlock if needed. */ static void cmd(uint16 cmd, unsigned long off) { int i; unlock_cmd_t *ul = NULL; ASSERT(flashutl_cmd != NULL); switch (flashutl_cmd->type) { case AMD: ul = &unlock_cmd_amd; break; case SST: ul = &unlock_cmd_sst; break; default: break; } if (flashutl_cmd->need_unlock) { ASSERT(ul); for (i = 0; i < UNLOCK_CMD_WORDS; i++) flash_writeword(FLASH_ADDR(ul->addr[i]), ul->cmd[i]); } /* cmd |= cmd << 8; */ if (off == CMD_ADDR) { switch (flashutl_cmd->type) { case AMD: off = AMD_CMD; break; case SST: off = SST_CMD; break; default: off = 0; break; } } #ifdef MIPSEB off ^= 2; #endif /* MIPSEB */ flash_writeword(FLASH_ADDR(off), cmd); } static void flash_reset() { ASSERT(flashutl_desc != NULL); if (flashutl_cmd->clear_csr) scmd(flashutl_cmd->clear_csr, 0); if (flashutl_cmd->read_array) scmd(flashutl_cmd->read_array, 0); } static int flash_poll(unsigned long off, uint16 data) { unsigned long addr; int cnt = FLASH_TRIES; uint16 st; ASSERT(flashutl_desc != NULL); if (flashutl_desc->type == AMD || flashutl_desc->type == SST) { /* AMD style poll checkes the address being written */ addr = FLASH_ADDR(off); while ((st = flash_readword(addr)) != data && cnt != 0) cnt--; if (cnt == 0) { DPRINT(("flash_poll: timeout, off %lx, read 0x%x, expected 0x%x\n", off, st, data)); return -1; } } else { /* INTEL style poll is at second word of the block being written */ addr = FLASH_ADDR(block(off, BLOCK_BASE)+sizeof(uint16)); while (((st = flash_readword(addr)) & DONE) == 0 && cnt != 0) cnt--; if (cnt == 0) { DPRINT(("flash_poll: timeout, error status = 0x%x\n", st)); return -1; } } return 0; } static unsigned long block(unsigned long addr, int which) { unsigned long b, l, sb; uint* sblocks; int i; ASSERT(flashutl_desc != NULL); ASSERT(addr < (unsigned long)flashutl_desc->size); b = addr / flashutl_desc->bsize; /* check for an address a full size block */ if (b >= flashutl_desc->ff && b <= flashutl_desc->lf) { if (which == BLOCK_LIM) b++; return (b * flashutl_desc->bsize); } /* search for the sub-block */ if (flashutl_desc->ff == 0) { /* sub blocks are at the end of the flash */ sb = flashutl_desc->bsize * (flashutl_desc->lf + 1); } else { /* sub blocks are at the start of the flash */ sb = 0; } sblocks = flashutl_desc->subblocks; for (i = 0; i < flashutl_desc->nsub; i++) { b = sb + sblocks[i]; l = sb + sblocks[i+1]; if (addr >= b && addr < l) { if (which == BLOCK_BASE) return b; else return l; } } return 0; } void nvWrite(unsigned short *data, unsigned int len) { uint off = flashutl_desc->size - NVRAM_SPACE; sysFlashWrite(off, (uchar*)data, len); } void nvWriteChars(unsigned char *data, unsigned int len) { uint off = flashutl_desc->size - NVRAM_SPACE; int err; if (flashutl_cmd->type == SFLASH) err = sflash_commit(sbh, cc, off, len, data); else /* PFLASH */ err = flash_write(off, data, len); if (err) DPRINT(("nvWriteChars failed\n")); else DPRINT(("nvWriteChars succeeded\n")); } int sysFlashErase(uint off, unsigned int numbytes) { unsigned long end = off + numbytes; int err = 0; if (flashutl_cmd->type == SFLASH) { err = sflash_commit(sbh, cc, off, numbytes, NULL); } else { while (off < end) { err = flash_eraseblk(off); if (err) break; off = block(off, BLOCK_LIM); } } if (err) DPRINT(("Block erase at 0x%x failed\n", off)); else DPRINT(("Done\n")); return !err; } int sysFlashWrite(uint off, uchar *src, uint numbytes) { int err; DPRINT(("Writing 0x%x bytes to flash @0x%x ...\n", (unsigned int)numbytes, off)); if (flashutl_cmd->type == SFLASH) err = sflash_commit(sbh, cc, off, numbytes, src); else { if (!sysFlashErase(off, numbytes)) return 0; err = flash_write(off, src, numbytes); } if (err) DPRINT(("Flash write failed\n")); else DPRINT(("Flash write succeeded\n")); return !err; } int sysFlashRead(uint off, uchar *buf, uint numbytes) { uint read, total_read = 0; if (flashutl_cmd->type == SFLASH) { while (numbytes) { read = sflash_read(sbh, cc, off, numbytes, buf); numbytes -= read; buf += read; off += read; total_read += read; } } else { ASSERT(!(off & (flashutl_wsz - 1))); ASSERT(!(numbytes & (flashutl_wsz - 1))); while (numbytes) { flash_writeword((unsigned long)buf, flash_readword(FLASH_ADDR(off))); numbytes -= flashutl_wsz; buf += flashutl_wsz; off += flashutl_wsz; total_read += flashutl_wsz; } } return (total_read); }