diff options
author | Jo-Philipp Wich <jow@openwrt.org> | 2010-01-16 15:11:52 +0000 |
---|---|---|
committer | Jo-Philipp Wich <jow@openwrt.org> | 2010-01-16 15:11:52 +0000 |
commit | 2a4a1eeae38fe70ff5e28682ce3ee2be32a3ba3b (patch) | |
tree | 8e1fe85799647f897b5eb9ae0b8ee3a6130120e2 /target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c | |
parent | 39f2b6d957cd512d95104656da0df5852e463308 (diff) | |
download | upstream-2a4a1eeae38fe70ff5e28682ce3ee2be32a3ba3b.tar.gz upstream-2a4a1eeae38fe70ff5e28682ce3ee2be32a3ba3b.tar.bz2 upstream-2a4a1eeae38fe70ff5e28682ce3ee2be32a3ba3b.zip |
fix serial flash support (#6442)
SVN-Revision: 19171
Diffstat (limited to 'target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c')
-rw-r--r-- | target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c b/target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c new file mode 100644 index 0000000000..9bfb8b42a2 --- /dev/null +++ b/target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c @@ -0,0 +1,508 @@ +/* + * Broadcom SiliconBackplane chipcommon serial flash interface + * + * 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$ + */ + +#include <typedefs.h> +#include <osl.h> +#include "include/bcmutils.h" +#include <sbutils.h> +#include <sbconfig.h> +#include <sbchipc.h> +#include <bcmdevs.h> +#include <sflash.h> + +/* Private global state */ +static struct sflash sflash; + +/* Issue a serial flash command */ +static INLINE void +sflash_cmd(osl_t *osh, chipcregs_t *cc, uint opcode) +{ + W_REG(osh, &cc->flashcontrol, SFLASH_START | opcode); + while (R_REG(osh, &cc->flashcontrol) & SFLASH_BUSY); +} + +/* Initialize serial flash access */ +struct sflash * +sflash_init(sb_t *sbh, chipcregs_t *cc) +{ + uint32 id, id2; + osl_t *osh; + + ASSERT(sbh); + + osh = sb_osh(sbh); + + bzero(&sflash, sizeof(sflash)); + + sflash.type = sbh->cccaps & CC_CAP_FLASH_MASK; + + switch (sflash.type) { + case SFLASH_ST: + /* Probe for ST chips */ + sflash_cmd(osh, cc, SFLASH_ST_DP); + sflash_cmd(osh, cc, SFLASH_ST_RES); + id = R_REG(osh, &cc->flashdata); + switch (id) { + case 0x11: + /* ST M25P20 2 Mbit Serial Flash */ + sflash.blocksize = 64 * 1024; + sflash.numblocks = 4; + break; + case 0x12: + /* ST M25P40 4 Mbit Serial Flash */ + sflash.blocksize = 64 * 1024; + sflash.numblocks = 8; + break; + case 0x13: + /* ST M25P80 8 Mbit Serial Flash */ + sflash.blocksize = 64 * 1024; + sflash.numblocks = 16; + break; + case 0x14: + /* ST M25P16 16 Mbit Serial Flash */ + sflash.blocksize = 64 * 1024; + sflash.numblocks = 32; + break; + case 0x15: + /* ST M25P32 32 Mbit Serial Flash */ + sflash.blocksize = 64 * 1024; + sflash.numblocks = 64; + break; + case 0x16: + /* ST M25P64 64 Mbit Serial Flash */ + sflash.blocksize = 64 * 1024; + sflash.numblocks = 128; + break; + case 0xbf: + W_REG(osh, &cc->flashaddress, 1); + sflash_cmd(osh, cc, SFLASH_ST_RES); + id2 = R_REG(osh, &cc->flashdata); + if (id2 == 0x44) { + /* SST M25VF80 4 Mbit Serial Flash */ + sflash.blocksize = 64 * 1024; + sflash.numblocks = 8; + } + break; + } + break; + + case SFLASH_AT: + /* Probe for Atmel chips */ + sflash_cmd(osh, cc, SFLASH_AT_STATUS); + id = R_REG(osh, &cc->flashdata) & 0x3c; + switch (id) { + case 0xc: + /* Atmel AT45DB011 1Mbit Serial Flash */ + sflash.blocksize = 256; + sflash.numblocks = 512; + break; + case 0x14: + /* Atmel AT45DB021 2Mbit Serial Flash */ + sflash.blocksize = 256; + sflash.numblocks = 1024; + break; + case 0x1c: + /* Atmel AT45DB041 4Mbit Serial Flash */ + sflash.blocksize = 256; + sflash.numblocks = 2048; + break; + case 0x24: + /* Atmel AT45DB081 8Mbit Serial Flash */ + sflash.blocksize = 256; + sflash.numblocks = 4096; + break; + case 0x2c: + /* Atmel AT45DB161 16Mbit Serial Flash */ + sflash.blocksize = 512; + sflash.numblocks = 4096; + break; + case 0x34: + /* Atmel AT45DB321 32Mbit Serial Flash */ + sflash.blocksize = 512; + sflash.numblocks = 8192; + break; + case 0x3c: + /* Atmel AT45DB642 64Mbit Serial Flash */ + sflash.blocksize = 1024; + sflash.numblocks = 8192; + break; + } + break; + } + + sflash.size = sflash.blocksize * sflash.numblocks; + return sflash.size ? &sflash : NULL; +} + +/* Read len bytes starting at offset into buf. Returns number of bytes read. */ +int +sflash_read(sb_t *sbh, chipcregs_t *cc, uint offset, uint len, uchar *buf) +{ + uint8 *from, *to; + int cnt, i; + osl_t *osh; + + ASSERT(sbh); + + if (!len) + return 0; + + if ((offset + len) > sflash.size) + return -22; + + if ((len >= 4) && (offset & 3)) + cnt = 4 - (offset & 3); + else if ((len >= 4) && ((uintptr)buf & 3)) + cnt = 4 - ((uintptr)buf & 3); + else + cnt = len; + + osh = sb_osh(sbh); + + from = (uint8 *)(uintptr)OSL_UNCACHED(SB_FLASH2 + offset); + to = (uint8 *)buf; + + if (cnt < 4) { + for (i = 0; i < cnt; i ++) { + *to = R_REG(osh, from); + from ++; + to ++; + } + return cnt; + } + + while (cnt >= 4) { + *(uint32 *)to = R_REG(osh, (uint32 *)from); + from += 4; + to += 4; + cnt -= 4; + } + + return (len - cnt); +} + +/* Poll for command completion. Returns zero when complete. */ +int +sflash_poll(sb_t *sbh, chipcregs_t *cc, uint offset) +{ + osl_t *osh; + + ASSERT(sbh); + + osh = sb_osh(sbh); + + if (offset >= sflash.size) + return -22; + + switch (sflash.type) { + case SFLASH_ST: + /* Check for ST Write In Progress bit */ + sflash_cmd(osh, cc, SFLASH_ST_RDSR); + return R_REG(osh, &cc->flashdata) & SFLASH_ST_WIP; + case SFLASH_AT: + /* Check for Atmel Ready bit */ + sflash_cmd(osh, cc, SFLASH_AT_STATUS); + return !(R_REG(osh, &cc->flashdata) & SFLASH_AT_READY); + } + + return 0; +} + +/* Write len bytes starting at offset into buf. Returns number of bytes + * written. Caller should poll for completion. + */ +int +sflash_write(sb_t *sbh, chipcregs_t *cc, uint offset, uint len, const uchar *buf) +{ + struct sflash *sfl; + int ret = 0; + bool is4712b0; + uint32 page, byte, mask; + osl_t *osh; + + ASSERT(sbh); + + osh = sb_osh(sbh); + + if (!len) + return 0; + + if ((offset + len) > sflash.size) + return -22; + + sfl = &sflash; + switch (sfl->type) { + case SFLASH_ST: + is4712b0 = (sbh->chip == BCM4712_CHIP_ID) && (sbh->chiprev == 3); + /* Enable writes */ + sflash_cmd(osh, cc, SFLASH_ST_WREN); + if (is4712b0) { + mask = 1 << 14; + W_REG(osh, &cc->flashaddress, offset); + W_REG(osh, &cc->flashdata, *buf++); + /* Set chip select */ + OR_REG(osh, &cc->gpioout, mask); + /* Issue a page program with the first byte */ + sflash_cmd(osh, cc, SFLASH_ST_PP); + ret = 1; + offset++; + len--; + while (len > 0) { + if ((offset & 255) == 0) { + /* Page boundary, drop cs and return */ + AND_REG(osh, &cc->gpioout, ~mask); + if (!sflash_poll(sbh, cc, offset)) { + /* Flash rejected command */ + return -11; + } + return ret; + } else { + /* Write single byte */ + sflash_cmd(osh, cc, *buf++); + } + ret++; + offset++; + len--; + } + /* All done, drop cs if needed */ + if ((offset & 255) != 1) { + /* Drop cs */ + AND_REG(osh, &cc->gpioout, ~mask); + if (!sflash_poll(sbh, cc, offset)) { + /* Flash rejected command */ + return -12; + } + } + } else if ( (sbh->ccrev >= 20) && (len != 1) ) { + //} else if ( sbh->ccrev >= 20 ) { /* foxconn modified by EricHuang, 05/24/2007 */ + W_REG(NULL, &cc->flashaddress, offset); + W_REG(NULL, &cc->flashdata, *buf++); + /* Issue a page program with CSA bit set */ + sflash_cmd(osh, cc, SFLASH_ST_CSA | SFLASH_ST_PP); + ret = 1; + offset++; + len--; + while (len > 0) { + if ((offset & 255) == 0) { + /* Page boundary, poll droping cs and return */ + W_REG(NULL, &cc->flashcontrol, 0); + /* wklin added start, 06/08/2007 */ + W_REG(NULL, &cc->flashcontrol, 0); + OSL_DELAY(1); + /* wklin added end, 06/08/2007 */ + /* wklin rmeoved start, 06/08/2007 */ +#if 0 + if (!sflash_poll(sbh, cc, offset)) { + /* Flash rejected command */ + return -11; + } +#endif + /* wklin removed end, 06/08/2007 */ + return ret; + } else { + /* Write single byte */ + sflash_cmd(osh, cc, SFLASH_ST_CSA | *buf++); + } + ret++; + offset++; + len--; + } + /* All done, drop cs if needed */ + if ((offset & 255) != 1) { + /* Drop cs, poll */ + W_REG(NULL, &cc->flashcontrol, 0); + /* wklin added start, 06/08/2007 */ + W_REG(NULL, &cc->flashcontrol, 0); + OSL_DELAY(1); + /* wklin added end, 06/08/2007 */ + /* wklin removed start, 06/08/2007 */ +#if 0 + if (!sflash_poll(sbh, cc, offset)) { + /* Flash rejected command */ + return -12; + } +#endif + /* wklin removed end, 06/08/2007 */ + } + } else { + ret = 1; + W_REG(osh, &cc->flashaddress, offset); + W_REG(osh, &cc->flashdata, *buf); + /* Page program */ + sflash_cmd(osh, cc, SFLASH_ST_PP); + } + break; + case SFLASH_AT: + mask = sfl->blocksize - 1; + page = (offset & ~mask) << 1; + byte = offset & mask; + /* Read main memory page into buffer 1 */ + if (byte || (len < sfl->blocksize)) { + W_REG(osh, &cc->flashaddress, page); + sflash_cmd(osh, cc, SFLASH_AT_BUF1_LOAD); + /* 250 us for AT45DB321B */ + SPINWAIT(sflash_poll(sbh, cc, offset), 1000); + ASSERT(!sflash_poll(sbh, cc, offset)); + } + /* Write into buffer 1 */ + for (ret = 0; (ret < (int)len) && (byte < sfl->blocksize); ret++) { + W_REG(osh, &cc->flashaddress, byte++); + W_REG(osh, &cc->flashdata, *buf++); + sflash_cmd(osh, cc, SFLASH_AT_BUF1_WRITE); + } + /* Write buffer 1 into main memory page */ + W_REG(osh, &cc->flashaddress, page); + sflash_cmd(osh, cc, SFLASH_AT_BUF1_PROGRAM); + break; + } + + return ret; +} + +/* Erase a region. Returns number of bytes scheduled for erasure. + * Caller should poll for completion. + */ +int +sflash_erase(sb_t *sbh, chipcregs_t *cc, uint offset) +{ + struct sflash *sfl; + osl_t *osh; + + ASSERT(sbh); + + osh = sb_osh(sbh); + + if (offset >= sflash.size) + return -22; + + sfl = &sflash; + switch (sfl->type) { + case SFLASH_ST: + sflash_cmd(osh, cc, SFLASH_ST_WREN); + W_REG(osh, &cc->flashaddress, offset); + sflash_cmd(osh, cc, SFLASH_ST_SE); + return sfl->blocksize; + case SFLASH_AT: + W_REG(osh, &cc->flashaddress, offset << 1); + sflash_cmd(osh, cc, SFLASH_AT_PAGE_ERASE); + return sfl->blocksize; + } + + return 0; +} + +/* + * writes the appropriate range of flash, a NULL buf simply erases + * the region of flash + */ +int +sflash_commit(sb_t *sbh, chipcregs_t *cc, uint offset, uint len, const uchar *buf) +{ + struct sflash *sfl; + uchar *block = NULL, *cur_ptr, *blk_ptr; + uint blocksize = 0, mask, cur_offset, cur_length, cur_retlen, remainder; + uint blk_offset, blk_len, copied; + int bytes, ret = 0; + osl_t *osh; + + ASSERT(sbh); + + osh = sb_osh(sbh); + + /* Check address range */ + if (len <= 0) + return 0; + + sfl = &sflash; + if ((offset + len) > sfl->size) + return -1; + + blocksize = sfl->blocksize; + mask = blocksize - 1; + + /* Allocate a block of mem */ + if (!(block = MALLOC(osh, blocksize))) + return -1; + + while (len) { + /* Align offset */ + cur_offset = offset & ~mask; + cur_length = blocksize; + cur_ptr = block; + + remainder = blocksize - (offset & mask); + if (len < remainder) + cur_retlen = len; + else + cur_retlen = remainder; + + /* buf == NULL means erase only */ + if (buf) { + /* Copy existing data into holding block if necessary */ + if ((offset & mask) || (len < blocksize)) { + blk_offset = cur_offset; + blk_len = cur_length; + blk_ptr = cur_ptr; + + /* Copy entire block */ + while (blk_len) { + copied = sflash_read(sbh, cc, blk_offset, blk_len, blk_ptr); + blk_offset += copied; + blk_len -= copied; + blk_ptr += copied; + } + } + + /* Copy input data into holding block */ + memcpy(cur_ptr + (offset & mask), buf, cur_retlen); + } + + /* Erase block */ + if ((ret = sflash_erase(sbh, cc, (uint) cur_offset)) < 0) + goto done; + while (sflash_poll(sbh, cc, (uint) cur_offset)); + + /* buf == NULL means erase only */ + if (!buf) { + offset += cur_retlen; + len -= cur_retlen; + continue; + } + + /* Write holding block */ + while (cur_length > 0) { + if ((bytes = sflash_write(sbh, cc, + (uint) cur_offset, + (uint) cur_length, + (uchar *) cur_ptr)) < 0) { + ret = bytes; + goto done; + } + while (sflash_poll(sbh, cc, (uint) cur_offset)); + cur_offset += bytes; + cur_length -= bytes; + cur_ptr += bytes; + } + + offset += cur_retlen; + len -= cur_retlen; + buf += cur_retlen; + } + + ret = len; +done: + if (block) + MFREE(osh, block, blocksize); + return ret; +} |