mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-29 10:08:59 +00:00
509 lines
12 KiB
C
509 lines
12 KiB
C
|
/*
|
||
|
* 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;
|
||
|
}
|