Openwrt/target/linux/brcm-2.4/files/drivers/mtd/devices/sflash.c
2009-04-17 14:09:46 +00:00

531 lines
11 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.
*
*/
#include <typedefs.h>
#include <osl.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)
{
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);
if (!sflash_poll (sbh, cc, offset))
{
/* Flash rejected command */
return -11;
}
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);
if (!sflash_poll (sbh, cc, offset))
{
/* Flash rejected command */
return -12;
}
}
}
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;
}