summaryrefslogtreecommitdiffstats
path: root/target/linux/brcm-2.4/files/arch/mips/bcm947xx/sflash.c
diff options
context:
space:
mode:
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.c508
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 000000000..9bfb8b42a
--- /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;
+}