mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-31 05:54:26 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			531 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
| }
 |