2014-02-24 18:37:37 +08:00
/*
2014-04-10 16:27:28 +08:00
* Based on m25p80 . c , by Mike Lavender ( mike @ steroidmicros . com ) , with
* influence from lart . c ( Abraham Van Der Merwe ) and mtd_dataflash . c
*
* Copyright ( C ) 2005 , Intec Automation Inc .
* Copyright ( C ) 2014 , Freescale Semiconductor , Inc .
2014-02-24 18:37:37 +08:00
*
* This code is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/err.h>
# include <linux/errno.h>
# include <linux/module.h>
# include <linux/device.h>
# include <linux/mutex.h>
# include <linux/math64.h>
# include <linux/mtd/cfi.h>
# include <linux/mtd/mtd.h>
# include <linux/of_platform.h>
# include <linux/spi/flash.h>
# include <linux/mtd/spi-nor.h>
/* Define max times to check status register before we give up. */
# define MAX_READY_WAIT_JIFFIES (40 * HZ) /* M25P16 specs 40s max chip erase */
# define JEDEC_MFR(_jedec_id) ((_jedec_id) >> 16)
/*
* Read the status register , returning its value in the location
* Return the status register value .
* Returns negative if error occurred .
*/
static int read_sr ( struct spi_nor * nor )
{
int ret ;
u8 val ;
2014-04-08 18:15:31 -07:00
ret = nor - > read_reg ( nor , SPINOR_OP_RDSR , & val , 1 ) ;
2014-02-24 18:37:37 +08:00
if ( ret < 0 ) {
pr_err ( " error %d reading SR \n " , ( int ) ret ) ;
return ret ;
}
return val ;
}
/*
* Read configuration register , returning its value in the
* location . Return the configuration register value .
* Returns negative if error occured .
*/
static int read_cr ( struct spi_nor * nor )
{
int ret ;
u8 val ;
2014-04-08 18:15:31 -07:00
ret = nor - > read_reg ( nor , SPINOR_OP_RDCR , & val , 1 ) ;
2014-02-24 18:37:37 +08:00
if ( ret < 0 ) {
dev_err ( nor - > dev , " error %d reading CR \n " , ret ) ;
return ret ;
}
return val ;
}
/*
* Dummy Cycle calculation for different type of read .
* It can be used to support more commands with
* different dummy cycle requirements .
*/
static inline int spi_nor_read_dummy_cycles ( struct spi_nor * nor )
{
switch ( nor - > flash_read ) {
case SPI_NOR_FAST :
case SPI_NOR_DUAL :
case SPI_NOR_QUAD :
return 1 ;
case SPI_NOR_NORMAL :
return 0 ;
}
return 0 ;
}
/*
* Write status register 1 byte
* Returns negative if error occurred .
*/
static inline int write_sr ( struct spi_nor * nor , u8 val )
{
nor - > cmd_buf [ 0 ] = val ;
2014-04-08 18:15:31 -07:00
return nor - > write_reg ( nor , SPINOR_OP_WRSR , nor - > cmd_buf , 1 , 0 ) ;
2014-02-24 18:37:37 +08:00
}
/*
* Set write enable latch with Write Enable command .
* Returns negative if error occurred .
*/
static inline int write_enable ( struct spi_nor * nor )
{
2014-04-08 18:15:31 -07:00
return nor - > write_reg ( nor , SPINOR_OP_WREN , NULL , 0 , 0 ) ;
2014-02-24 18:37:37 +08:00
}
/*
* Send write disble instruction to the chip .
*/
static inline int write_disable ( struct spi_nor * nor )
{
2014-04-08 18:15:31 -07:00
return nor - > write_reg ( nor , SPINOR_OP_WRDI , NULL , 0 , 0 ) ;
2014-02-24 18:37:37 +08:00
}
static inline struct spi_nor * mtd_to_spi_nor ( struct mtd_info * mtd )
{
return mtd - > priv ;
}
/* Enable/disable 4-byte addressing mode. */
static inline int set_4byte ( struct spi_nor * nor , u32 jedec_id , int enable )
{
int status ;
bool need_wren = false ;
u8 cmd ;
switch ( JEDEC_MFR ( jedec_id ) ) {
case CFI_MFR_ST : /* Micron, actually */
/* Some Micron need WREN command; all will accept it */
need_wren = true ;
case CFI_MFR_MACRONIX :
case 0xEF /* winbond */ :
if ( need_wren )
write_enable ( nor ) ;
2014-04-08 18:15:31 -07:00
cmd = enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B ;
2014-02-24 18:37:37 +08:00
status = nor - > write_reg ( nor , cmd , NULL , 0 , 0 ) ;
if ( need_wren )
write_disable ( nor ) ;
return status ;
default :
/* Spansion style */
nor - > cmd_buf [ 0 ] = enable < < 7 ;
2014-04-08 18:15:31 -07:00
return nor - > write_reg ( nor , SPINOR_OP_BRWR , nor - > cmd_buf , 1 , 0 ) ;
2014-02-24 18:37:37 +08:00
}
}
static int spi_nor_wait_till_ready ( struct spi_nor * nor )
{
unsigned long deadline ;
int sr ;
deadline = jiffies + MAX_READY_WAIT_JIFFIES ;
do {
cond_resched ( ) ;
sr = read_sr ( nor ) ;
if ( sr < 0 )
break ;
else if ( ! ( sr & SR_WIP ) )
return 0 ;
} while ( ! time_after_eq ( jiffies , deadline ) ) ;
return - ETIMEDOUT ;
}
/*
* Service routine to read status register until ready , or timeout occurs .
* Returns non - zero if error .
*/
static int wait_till_ready ( struct spi_nor * nor )
{
return nor - > wait_till_ready ( nor ) ;
}
/*
* Erase the whole flash memory
*
* Returns 0 if successful , non - zero otherwise .
*/
static int erase_chip ( struct spi_nor * nor )
{
int ret ;
dev_dbg ( nor - > dev , " %lldKiB \n " , ( long long ) ( nor - > mtd - > size > > 10 ) ) ;
/* Wait until finished previous write command. */
ret = wait_till_ready ( nor ) ;
if ( ret )
return ret ;
/* Send write enable, then erase commands. */
write_enable ( nor ) ;
2014-04-08 18:15:31 -07:00
return nor - > write_reg ( nor , SPINOR_OP_CHIP_ERASE , NULL , 0 , 0 ) ;
2014-02-24 18:37:37 +08:00
}
static int spi_nor_lock_and_prep ( struct spi_nor * nor , enum spi_nor_ops ops )
{
int ret = 0 ;
mutex_lock ( & nor - > lock ) ;
if ( nor - > prepare ) {
ret = nor - > prepare ( nor , ops ) ;
if ( ret ) {
dev_err ( nor - > dev , " failed in the preparation. \n " ) ;
mutex_unlock ( & nor - > lock ) ;
return ret ;
}
}
return ret ;
}
static void spi_nor_unlock_and_unprep ( struct spi_nor * nor , enum spi_nor_ops ops )
{
if ( nor - > unprepare )
nor - > unprepare ( nor , ops ) ;
mutex_unlock ( & nor - > lock ) ;
}
/*
* Erase an address range on the nor chip . The address range may extend
* one or more erase sectors . Return an error is there is a problem erasing .
*/
static int spi_nor_erase ( struct mtd_info * mtd , struct erase_info * instr )
{
struct spi_nor * nor = mtd_to_spi_nor ( mtd ) ;
u32 addr , len ;
uint32_t rem ;
int ret ;
dev_dbg ( nor - > dev , " at 0x%llx, len %lld \n " , ( long long ) instr - > addr ,
( long long ) instr - > len ) ;
div_u64_rem ( instr - > len , mtd - > erasesize , & rem ) ;
if ( rem )
return - EINVAL ;
addr = instr - > addr ;
len = instr - > len ;
ret = spi_nor_lock_and_prep ( nor , SPI_NOR_OPS_ERASE ) ;
if ( ret )
return ret ;
/* whole-chip erase? */
if ( len = = mtd - > size ) {
if ( erase_chip ( nor ) ) {
ret = - EIO ;
goto erase_err ;
}
/* REVISIT in some cases we could speed up erasing large regions
2014-04-08 18:15:31 -07:00
* by using SPINOR_OP_SE instead of SPINOR_OP_BE_4K . We may have set up
2014-02-24 18:37:37 +08:00
* to use " small sector erase " , but that ' s not always optimal .
*/
/* "sector"-at-a-time erase */
} else {
while ( len ) {
if ( nor - > erase ( nor , addr ) ) {
ret = - EIO ;
goto erase_err ;
}
addr + = mtd - > erasesize ;
len - = mtd - > erasesize ;
}
}
spi_nor_unlock_and_unprep ( nor , SPI_NOR_OPS_ERASE ) ;
instr - > state = MTD_ERASE_DONE ;
mtd_erase_callback ( instr ) ;
return ret ;
erase_err :
spi_nor_unlock_and_unprep ( nor , SPI_NOR_OPS_ERASE ) ;
instr - > state = MTD_ERASE_FAILED ;
return ret ;
}
static int spi_nor_lock ( struct mtd_info * mtd , loff_t ofs , uint64_t len )
{
struct spi_nor * nor = mtd_to_spi_nor ( mtd ) ;
uint32_t offset = ofs ;
uint8_t status_old , status_new ;
int ret = 0 ;
ret = spi_nor_lock_and_prep ( nor , SPI_NOR_OPS_LOCK ) ;
if ( ret )
return ret ;
/* Wait until finished previous command */
ret = wait_till_ready ( nor ) ;
if ( ret )
goto err ;
status_old = read_sr ( nor ) ;
if ( offset < mtd - > size - ( mtd - > size / 2 ) )
status_new = status_old | SR_BP2 | SR_BP1 | SR_BP0 ;
else if ( offset < mtd - > size - ( mtd - > size / 4 ) )
status_new = ( status_old & ~ SR_BP0 ) | SR_BP2 | SR_BP1 ;
else if ( offset < mtd - > size - ( mtd - > size / 8 ) )
status_new = ( status_old & ~ SR_BP1 ) | SR_BP2 | SR_BP0 ;
else if ( offset < mtd - > size - ( mtd - > size / 16 ) )
status_new = ( status_old & ~ ( SR_BP0 | SR_BP1 ) ) | SR_BP2 ;
else if ( offset < mtd - > size - ( mtd - > size / 32 ) )
status_new = ( status_old & ~ SR_BP2 ) | SR_BP1 | SR_BP0 ;
else if ( offset < mtd - > size - ( mtd - > size / 64 ) )
status_new = ( status_old & ~ ( SR_BP2 | SR_BP0 ) ) | SR_BP1 ;
else
status_new = ( status_old & ~ ( SR_BP2 | SR_BP1 ) ) | SR_BP0 ;
/* Only modify protection if it will not unlock other areas */
if ( ( status_new & ( SR_BP2 | SR_BP1 | SR_BP0 ) ) >
( status_old & ( SR_BP2 | SR_BP1 | SR_BP0 ) ) ) {
write_enable ( nor ) ;
ret = write_sr ( nor , status_new ) ;
if ( ret )
goto err ;
}
err :
spi_nor_unlock_and_unprep ( nor , SPI_NOR_OPS_LOCK ) ;
return ret ;
}
static int spi_nor_unlock ( struct mtd_info * mtd , loff_t ofs , uint64_t len )
{
struct spi_nor * nor = mtd_to_spi_nor ( mtd ) ;
uint32_t offset = ofs ;
uint8_t status_old , status_new ;
int ret = 0 ;
ret = spi_nor_lock_and_prep ( nor , SPI_NOR_OPS_UNLOCK ) ;
if ( ret )
return ret ;
/* Wait until finished previous command */
ret = wait_till_ready ( nor ) ;
if ( ret )
goto err ;
status_old = read_sr ( nor ) ;
if ( offset + len > mtd - > size - ( mtd - > size / 64 ) )
status_new = status_old & ~ ( SR_BP2 | SR_BP1 | SR_BP0 ) ;
else if ( offset + len > mtd - > size - ( mtd - > size / 32 ) )
status_new = ( status_old & ~ ( SR_BP2 | SR_BP1 ) ) | SR_BP0 ;
else if ( offset + len > mtd - > size - ( mtd - > size / 16 ) )
status_new = ( status_old & ~ ( SR_BP2 | SR_BP0 ) ) | SR_BP1 ;
else if ( offset + len > mtd - > size - ( mtd - > size / 8 ) )
status_new = ( status_old & ~ SR_BP2 ) | SR_BP1 | SR_BP0 ;
else if ( offset + len > mtd - > size - ( mtd - > size / 4 ) )
status_new = ( status_old & ~ ( SR_BP0 | SR_BP1 ) ) | SR_BP2 ;
else if ( offset + len > mtd - > size - ( mtd - > size / 2 ) )
status_new = ( status_old & ~ SR_BP1 ) | SR_BP2 | SR_BP0 ;
else
status_new = ( status_old & ~ SR_BP0 ) | SR_BP2 | SR_BP1 ;
/* Only modify protection if it will not lock other areas */
if ( ( status_new & ( SR_BP2 | SR_BP1 | SR_BP0 ) ) <
( status_old & ( SR_BP2 | SR_BP1 | SR_BP0 ) ) ) {
write_enable ( nor ) ;
ret = write_sr ( nor , status_new ) ;
if ( ret )
goto err ;
}
err :
spi_nor_unlock_and_unprep ( nor , SPI_NOR_OPS_UNLOCK ) ;
return ret ;
}
struct flash_info {
/* JEDEC id zero means "no ID" (most older chips); otherwise it has
* a high byte of zero plus three data bytes : the manufacturer id ,
* then a two byte device id .
*/
u32 jedec_id ;
u16 ext_id ;
2014-04-08 18:15:31 -07:00
/* The size listed here is what works with SPINOR_OP_SE, which isn't
2014-02-24 18:37:37 +08:00
* necessarily called a " sector " by the vendor .
*/
unsigned sector_size ;
u16 n_sectors ;
u16 page_size ;
u16 addr_width ;
u16 flags ;
2014-04-08 18:15:31 -07:00
# define SECT_4K 0x01 /* SPINOR_OP_BE_4K works uniformly */
2014-02-24 18:37:37 +08:00
# define SPI_NOR_NO_ERASE 0x02 /* No erase command needed */
# define SST_WRITE 0x04 /* use SST byte programming */
# define SPI_NOR_NO_FR 0x08 /* Can't do fastread */
2014-04-08 18:15:31 -07:00
# define SECT_4K_PMC 0x10 /* SPINOR_OP_BE_4K_PMC works uniformly */
2014-02-24 18:37:37 +08:00
# define SPI_NOR_DUAL_READ 0x20 /* Flash supports Dual Read */
# define SPI_NOR_QUAD_READ 0x40 /* Flash supports Quad Read */
} ;
# define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
( ( kernel_ulong_t ) & ( struct flash_info ) { \
. jedec_id = ( _jedec_id ) , \
. ext_id = ( _ext_id ) , \
. sector_size = ( _sector_size ) , \
. n_sectors = ( _n_sectors ) , \
. page_size = 256 , \
. flags = ( _flags ) , \
} )
# define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_width, _flags) \
( ( kernel_ulong_t ) & ( struct flash_info ) { \
. sector_size = ( _sector_size ) , \
. n_sectors = ( _n_sectors ) , \
. page_size = ( _page_size ) , \
. addr_width = ( _addr_width ) , \
. flags = ( _flags ) , \
} )
/* NOTE: double check command sets and memory organization when you add
* more nor chips . This current list focusses on newer chips , which
* have been converging on command sets which including JEDEC ID .
*/
const struct spi_device_id spi_nor_ids [ ] = {
/* Atmel -- some are (confusingly) marketed as "DataFlash" */
{ " at25fs010 " , INFO ( 0x1f6601 , 0 , 32 * 1024 , 4 , SECT_4K ) } ,
{ " at25fs040 " , INFO ( 0x1f6604 , 0 , 64 * 1024 , 8 , SECT_4K ) } ,
{ " at25df041a " , INFO ( 0x1f4401 , 0 , 64 * 1024 , 8 , SECT_4K ) } ,
{ " at25df321a " , INFO ( 0x1f4701 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " at25df641 " , INFO ( 0x1f4800 , 0 , 64 * 1024 , 128 , SECT_4K ) } ,
{ " at26f004 " , INFO ( 0x1f0400 , 0 , 64 * 1024 , 8 , SECT_4K ) } ,
{ " at26df081a " , INFO ( 0x1f4501 , 0 , 64 * 1024 , 16 , SECT_4K ) } ,
{ " at26df161a " , INFO ( 0x1f4601 , 0 , 64 * 1024 , 32 , SECT_4K ) } ,
{ " at26df321 " , INFO ( 0x1f4700 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " at45db081d " , INFO ( 0x1f2500 , 0 , 64 * 1024 , 16 , SECT_4K ) } ,
/* EON -- en25xxx */
{ " en25f32 " , INFO ( 0x1c3116 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " en25p32 " , INFO ( 0x1c2016 , 0 , 64 * 1024 , 64 , 0 ) } ,
{ " en25q32b " , INFO ( 0x1c3016 , 0 , 64 * 1024 , 64 , 0 ) } ,
{ " en25p64 " , INFO ( 0x1c2017 , 0 , 64 * 1024 , 128 , 0 ) } ,
{ " en25q64 " , INFO ( 0x1c3017 , 0 , 64 * 1024 , 128 , SECT_4K ) } ,
{ " en25qh256 " , INFO ( 0x1c7019 , 0 , 64 * 1024 , 512 , 0 ) } ,
/* ESMT */
{ " f25l32pa " , INFO ( 0x8c2016 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
/* Everspin */
{ " mr25h256 " , CAT25_INFO ( 32 * 1024 , 1 , 256 , 2 , SPI_NOR_NO_ERASE | SPI_NOR_NO_FR ) } ,
{ " mr25h10 " , CAT25_INFO ( 128 * 1024 , 1 , 256 , 3 , SPI_NOR_NO_ERASE | SPI_NOR_NO_FR ) } ,
/* GigaDevice */
{ " gd25q32 " , INFO ( 0xc84016 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " gd25q64 " , INFO ( 0xc84017 , 0 , 64 * 1024 , 128 , SECT_4K ) } ,
/* Intel/Numonyx -- xxxs33b */
{ " 160s33b " , INFO ( 0x898911 , 0 , 64 * 1024 , 32 , 0 ) } ,
{ " 320s33b " , INFO ( 0x898912 , 0 , 64 * 1024 , 64 , 0 ) } ,
{ " 640s33b " , INFO ( 0x898913 , 0 , 64 * 1024 , 128 , 0 ) } ,
/* Macronix */
{ " mx25l2005a " , INFO ( 0xc22012 , 0 , 64 * 1024 , 4 , SECT_4K ) } ,
{ " mx25l4005a " , INFO ( 0xc22013 , 0 , 64 * 1024 , 8 , SECT_4K ) } ,
{ " mx25l8005 " , INFO ( 0xc22014 , 0 , 64 * 1024 , 16 , 0 ) } ,
{ " mx25l1606e " , INFO ( 0xc22015 , 0 , 64 * 1024 , 32 , SECT_4K ) } ,
{ " mx25l3205d " , INFO ( 0xc22016 , 0 , 64 * 1024 , 64 , 0 ) } ,
{ " mx25l3255e " , INFO ( 0xc29e16 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " mx25l6405d " , INFO ( 0xc22017 , 0 , 64 * 1024 , 128 , 0 ) } ,
{ " mx25l12805d " , INFO ( 0xc22018 , 0 , 64 * 1024 , 256 , 0 ) } ,
{ " mx25l12855e " , INFO ( 0xc22618 , 0 , 64 * 1024 , 256 , 0 ) } ,
{ " mx25l25635e " , INFO ( 0xc22019 , 0 , 64 * 1024 , 512 , 0 ) } ,
{ " mx25l25655e " , INFO ( 0xc22619 , 0 , 64 * 1024 , 512 , 0 ) } ,
{ " mx66l51235l " , INFO ( 0xc2201a , 0 , 64 * 1024 , 1024 , SPI_NOR_QUAD_READ ) } ,
{ " mx66l1g55g " , INFO ( 0xc2261b , 0 , 64 * 1024 , 2048 , SPI_NOR_QUAD_READ ) } ,
/* Micron */
{ " n25q064 " , INFO ( 0x20ba17 , 0 , 64 * 1024 , 128 , 0 ) } ,
{ " n25q128a11 " , INFO ( 0x20bb18 , 0 , 64 * 1024 , 256 , 0 ) } ,
{ " n25q128a13 " , INFO ( 0x20ba18 , 0 , 64 * 1024 , 256 , 0 ) } ,
{ " n25q256a " , INFO ( 0x20ba19 , 0 , 64 * 1024 , 512 , SECT_4K ) } ,
{ " n25q512a " , INFO ( 0x20bb20 , 0 , 64 * 1024 , 1024 , SECT_4K ) } ,
/* PMC */
{ " pm25lv512 " , INFO ( 0 , 0 , 32 * 1024 , 2 , SECT_4K_PMC ) } ,
{ " pm25lv010 " , INFO ( 0 , 0 , 32 * 1024 , 4 , SECT_4K_PMC ) } ,
{ " pm25lq032 " , INFO ( 0x7f9d46 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
/* Spansion -- single (large) sector size only, at least
* for the chips listed here ( without boot sectors ) .
*/
{ " s25sl032p " , INFO ( 0x010215 , 0x4d00 , 64 * 1024 , 64 , 0 ) } ,
{ " s25sl064p " , INFO ( 0x010216 , 0x4d00 , 64 * 1024 , 128 , 0 ) } ,
{ " s25fl256s0 " , INFO ( 0x010219 , 0x4d00 , 256 * 1024 , 128 , 0 ) } ,
{ " s25fl256s1 " , INFO ( 0x010219 , 0x4d01 , 64 * 1024 , 512 , SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ ) } ,
{ " s25fl512s " , INFO ( 0x010220 , 0x4d00 , 256 * 1024 , 256 , SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ ) } ,
{ " s70fl01gs " , INFO ( 0x010221 , 0x4d00 , 256 * 1024 , 256 , 0 ) } ,
{ " s25sl12800 " , INFO ( 0x012018 , 0x0300 , 256 * 1024 , 64 , 0 ) } ,
{ " s25sl12801 " , INFO ( 0x012018 , 0x0301 , 64 * 1024 , 256 , 0 ) } ,
{ " s25fl129p0 " , INFO ( 0x012018 , 0x4d00 , 256 * 1024 , 64 , 0 ) } ,
{ " s25fl129p1 " , INFO ( 0x012018 , 0x4d01 , 64 * 1024 , 256 , 0 ) } ,
{ " s25sl004a " , INFO ( 0x010212 , 0 , 64 * 1024 , 8 , 0 ) } ,
{ " s25sl008a " , INFO ( 0x010213 , 0 , 64 * 1024 , 16 , 0 ) } ,
{ " s25sl016a " , INFO ( 0x010214 , 0 , 64 * 1024 , 32 , 0 ) } ,
{ " s25sl032a " , INFO ( 0x010215 , 0 , 64 * 1024 , 64 , 0 ) } ,
{ " s25sl064a " , INFO ( 0x010216 , 0 , 64 * 1024 , 128 , 0 ) } ,
{ " s25fl008k " , INFO ( 0xef4014 , 0 , 64 * 1024 , 16 , SECT_4K ) } ,
{ " s25fl016k " , INFO ( 0xef4015 , 0 , 64 * 1024 , 32 , SECT_4K ) } ,
{ " s25fl064k " , INFO ( 0xef4017 , 0 , 64 * 1024 , 128 , SECT_4K ) } ,
/* SST -- large erase sizes are "overlays", "sectors" are 4K */
{ " sst25vf040b " , INFO ( 0xbf258d , 0 , 64 * 1024 , 8 , SECT_4K | SST_WRITE ) } ,
{ " sst25vf080b " , INFO ( 0xbf258e , 0 , 64 * 1024 , 16 , SECT_4K | SST_WRITE ) } ,
{ " sst25vf016b " , INFO ( 0xbf2541 , 0 , 64 * 1024 , 32 , SECT_4K | SST_WRITE ) } ,
{ " sst25vf032b " , INFO ( 0xbf254a , 0 , 64 * 1024 , 64 , SECT_4K | SST_WRITE ) } ,
{ " sst25vf064c " , INFO ( 0xbf254b , 0 , 64 * 1024 , 128 , SECT_4K ) } ,
{ " sst25wf512 " , INFO ( 0xbf2501 , 0 , 64 * 1024 , 1 , SECT_4K | SST_WRITE ) } ,
{ " sst25wf010 " , INFO ( 0xbf2502 , 0 , 64 * 1024 , 2 , SECT_4K | SST_WRITE ) } ,
{ " sst25wf020 " , INFO ( 0xbf2503 , 0 , 64 * 1024 , 4 , SECT_4K | SST_WRITE ) } ,
{ " sst25wf040 " , INFO ( 0xbf2504 , 0 , 64 * 1024 , 8 , SECT_4K | SST_WRITE ) } ,
/* ST Microelectronics -- newer production may have feature updates */
{ " m25p05 " , INFO ( 0x202010 , 0 , 32 * 1024 , 2 , 0 ) } ,
{ " m25p10 " , INFO ( 0x202011 , 0 , 32 * 1024 , 4 , 0 ) } ,
{ " m25p20 " , INFO ( 0x202012 , 0 , 64 * 1024 , 4 , 0 ) } ,
{ " m25p40 " , INFO ( 0x202013 , 0 , 64 * 1024 , 8 , 0 ) } ,
{ " m25p80 " , INFO ( 0x202014 , 0 , 64 * 1024 , 16 , 0 ) } ,
{ " m25p16 " , INFO ( 0x202015 , 0 , 64 * 1024 , 32 , 0 ) } ,
{ " m25p32 " , INFO ( 0x202016 , 0 , 64 * 1024 , 64 , 0 ) } ,
{ " m25p64 " , INFO ( 0x202017 , 0 , 64 * 1024 , 128 , 0 ) } ,
{ " m25p128 " , INFO ( 0x202018 , 0 , 256 * 1024 , 64 , 0 ) } ,
{ " n25q032 " , INFO ( 0x20ba16 , 0 , 64 * 1024 , 64 , 0 ) } ,
{ " m25p05-nonjedec " , INFO ( 0 , 0 , 32 * 1024 , 2 , 0 ) } ,
{ " m25p10-nonjedec " , INFO ( 0 , 0 , 32 * 1024 , 4 , 0 ) } ,
{ " m25p20-nonjedec " , INFO ( 0 , 0 , 64 * 1024 , 4 , 0 ) } ,
{ " m25p40-nonjedec " , INFO ( 0 , 0 , 64 * 1024 , 8 , 0 ) } ,
{ " m25p80-nonjedec " , INFO ( 0 , 0 , 64 * 1024 , 16 , 0 ) } ,
{ " m25p16-nonjedec " , INFO ( 0 , 0 , 64 * 1024 , 32 , 0 ) } ,
{ " m25p32-nonjedec " , INFO ( 0 , 0 , 64 * 1024 , 64 , 0 ) } ,
{ " m25p64-nonjedec " , INFO ( 0 , 0 , 64 * 1024 , 128 , 0 ) } ,
{ " m25p128-nonjedec " , INFO ( 0 , 0 , 256 * 1024 , 64 , 0 ) } ,
{ " m45pe10 " , INFO ( 0x204011 , 0 , 64 * 1024 , 2 , 0 ) } ,
{ " m45pe80 " , INFO ( 0x204014 , 0 , 64 * 1024 , 16 , 0 ) } ,
{ " m45pe16 " , INFO ( 0x204015 , 0 , 64 * 1024 , 32 , 0 ) } ,
{ " m25pe20 " , INFO ( 0x208012 , 0 , 64 * 1024 , 4 , 0 ) } ,
{ " m25pe80 " , INFO ( 0x208014 , 0 , 64 * 1024 , 16 , 0 ) } ,
{ " m25pe16 " , INFO ( 0x208015 , 0 , 64 * 1024 , 32 , SECT_4K ) } ,
{ " m25px16 " , INFO ( 0x207115 , 0 , 64 * 1024 , 32 , SECT_4K ) } ,
{ " m25px32 " , INFO ( 0x207116 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " m25px32-s0 " , INFO ( 0x207316 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " m25px32-s1 " , INFO ( 0x206316 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " m25px64 " , INFO ( 0x207117 , 0 , 64 * 1024 , 128 , 0 ) } ,
/* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */
{ " w25x10 " , INFO ( 0xef3011 , 0 , 64 * 1024 , 2 , SECT_4K ) } ,
{ " w25x20 " , INFO ( 0xef3012 , 0 , 64 * 1024 , 4 , SECT_4K ) } ,
{ " w25x40 " , INFO ( 0xef3013 , 0 , 64 * 1024 , 8 , SECT_4K ) } ,
{ " w25x80 " , INFO ( 0xef3014 , 0 , 64 * 1024 , 16 , SECT_4K ) } ,
{ " w25x16 " , INFO ( 0xef3015 , 0 , 64 * 1024 , 32 , SECT_4K ) } ,
{ " w25x32 " , INFO ( 0xef3016 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " w25q32 " , INFO ( 0xef4016 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " w25q32dw " , INFO ( 0xef6016 , 0 , 64 * 1024 , 64 , SECT_4K ) } ,
{ " w25x64 " , INFO ( 0xef3017 , 0 , 64 * 1024 , 128 , SECT_4K ) } ,
{ " w25q64 " , INFO ( 0xef4017 , 0 , 64 * 1024 , 128 , SECT_4K ) } ,
{ " w25q128 " , INFO ( 0xef4018 , 0 , 64 * 1024 , 256 , SECT_4K ) } ,
{ " w25q80 " , INFO ( 0xef5014 , 0 , 64 * 1024 , 16 , SECT_4K ) } ,
{ " w25q80bl " , INFO ( 0xef4014 , 0 , 64 * 1024 , 16 , SECT_4K ) } ,
{ " w25q128 " , INFO ( 0xef4018 , 0 , 64 * 1024 , 256 , SECT_4K ) } ,
{ " w25q256 " , INFO ( 0xef4019 , 0 , 64 * 1024 , 512 , SECT_4K ) } ,
/* Catalyst / On Semiconductor -- non-JEDEC */
{ " cat25c11 " , CAT25_INFO ( 16 , 8 , 16 , 1 , SPI_NOR_NO_ERASE | SPI_NOR_NO_FR ) } ,
{ " cat25c03 " , CAT25_INFO ( 32 , 8 , 16 , 2 , SPI_NOR_NO_ERASE | SPI_NOR_NO_FR ) } ,
{ " cat25c09 " , CAT25_INFO ( 128 , 8 , 32 , 2 , SPI_NOR_NO_ERASE | SPI_NOR_NO_FR ) } ,
{ " cat25c17 " , CAT25_INFO ( 256 , 8 , 32 , 2 , SPI_NOR_NO_ERASE | SPI_NOR_NO_FR ) } ,
{ " cat25128 " , CAT25_INFO ( 2048 , 8 , 64 , 2 , SPI_NOR_NO_ERASE | SPI_NOR_NO_FR ) } ,
{ } ,
} ;
2014-04-08 18:22:57 -07:00
EXPORT_SYMBOL_GPL ( spi_nor_ids ) ;
2014-02-24 18:37:37 +08:00
static const struct spi_device_id * spi_nor_read_id ( struct spi_nor * nor )
{
int tmp ;
u8 id [ 5 ] ;
u32 jedec ;
u16 ext_jedec ;
struct flash_info * info ;
2014-04-08 18:15:31 -07:00
tmp = nor - > read_reg ( nor , SPINOR_OP_RDID , id , 5 ) ;
2014-02-24 18:37:37 +08:00
if ( tmp < 0 ) {
dev_dbg ( nor - > dev , " error %d reading JEDEC ID \n " , tmp ) ;
return ERR_PTR ( tmp ) ;
}
jedec = id [ 0 ] ;
jedec = jedec < < 8 ;
jedec | = id [ 1 ] ;
jedec = jedec < < 8 ;
jedec | = id [ 2 ] ;
ext_jedec = id [ 3 ] < < 8 | id [ 4 ] ;
for ( tmp = 0 ; tmp < ARRAY_SIZE ( spi_nor_ids ) - 1 ; tmp + + ) {
info = ( void * ) spi_nor_ids [ tmp ] . driver_data ;
if ( info - > jedec_id = = jedec ) {
if ( info - > ext_id = = 0 | | info - > ext_id = = ext_jedec )
return & spi_nor_ids [ tmp ] ;
}
}
dev_err ( nor - > dev , " unrecognized JEDEC id %06x \n " , jedec ) ;
return ERR_PTR ( - ENODEV ) ;
}
static const struct spi_device_id * jedec_probe ( struct spi_nor * nor )
{
return nor - > read_id ( nor ) ;
}
static int spi_nor_read ( struct mtd_info * mtd , loff_t from , size_t len ,
size_t * retlen , u_char * buf )
{
struct spi_nor * nor = mtd_to_spi_nor ( mtd ) ;
int ret ;
dev_dbg ( nor - > dev , " from 0x%08x, len %zd \n " , ( u32 ) from , len ) ;
ret = spi_nor_lock_and_prep ( nor , SPI_NOR_OPS_READ ) ;
if ( ret )
return ret ;
ret = nor - > read ( nor , from , len , retlen , buf ) ;
spi_nor_unlock_and_unprep ( nor , SPI_NOR_OPS_READ ) ;
return ret ;
}
static int sst_write ( struct mtd_info * mtd , loff_t to , size_t len ,
size_t * retlen , const u_char * buf )
{
struct spi_nor * nor = mtd_to_spi_nor ( mtd ) ;
size_t actual ;
int ret ;
dev_dbg ( nor - > dev , " to 0x%08x, len %zd \n " , ( u32 ) to , len ) ;
ret = spi_nor_lock_and_prep ( nor , SPI_NOR_OPS_WRITE ) ;
if ( ret )
return ret ;
/* Wait until finished previous write command. */
ret = wait_till_ready ( nor ) ;
if ( ret )
goto time_out ;
write_enable ( nor ) ;
nor - > sst_write_second = false ;
actual = to % 2 ;
/* Start write from odd address. */
if ( actual ) {
2014-04-08 18:15:31 -07:00
nor - > program_opcode = SPINOR_OP_BP ;
2014-02-24 18:37:37 +08:00
/* write one byte. */
nor - > write ( nor , to , 1 , retlen , buf ) ;
ret = wait_till_ready ( nor ) ;
if ( ret )
goto time_out ;
}
to + = actual ;
/* Write out most of the data here. */
for ( ; actual < len - 1 ; actual + = 2 ) {
2014-04-08 18:15:31 -07:00
nor - > program_opcode = SPINOR_OP_AAI_WP ;
2014-02-24 18:37:37 +08:00
/* write two bytes. */
nor - > write ( nor , to , 2 , retlen , buf + actual ) ;
ret = wait_till_ready ( nor ) ;
if ( ret )
goto time_out ;
to + = 2 ;
nor - > sst_write_second = true ;
}
nor - > sst_write_second = false ;
write_disable ( nor ) ;
ret = wait_till_ready ( nor ) ;
if ( ret )
goto time_out ;
/* Write out trailing byte if it exists. */
if ( actual ! = len ) {
write_enable ( nor ) ;
2014-04-08 18:15:31 -07:00
nor - > program_opcode = SPINOR_OP_BP ;
2014-02-24 18:37:37 +08:00
nor - > write ( nor , to , 1 , retlen , buf + actual ) ;
ret = wait_till_ready ( nor ) ;
if ( ret )
goto time_out ;
write_disable ( nor ) ;
}
time_out :
spi_nor_unlock_and_unprep ( nor , SPI_NOR_OPS_WRITE ) ;
return ret ;
}
/*
* Write an address range to the nor chip . Data must be written in
* FLASH_PAGESIZE chunks . The address range may be any size provided
* it is within the physical boundaries .
*/
static int spi_nor_write ( struct mtd_info * mtd , loff_t to , size_t len ,
size_t * retlen , const u_char * buf )
{
struct spi_nor * nor = mtd_to_spi_nor ( mtd ) ;
u32 page_offset , page_size , i ;
int ret ;
dev_dbg ( nor - > dev , " to 0x%08x, len %zd \n " , ( u32 ) to , len ) ;
ret = spi_nor_lock_and_prep ( nor , SPI_NOR_OPS_WRITE ) ;
if ( ret )
return ret ;
/* Wait until finished previous write command. */
ret = wait_till_ready ( nor ) ;
if ( ret )
goto write_err ;
write_enable ( nor ) ;
page_offset = to & ( nor - > page_size - 1 ) ;
/* do all the bytes fit onto one page? */
if ( page_offset + len < = nor - > page_size ) {
nor - > write ( nor , to , len , retlen , buf ) ;
} else {
/* the size of data remaining on the first page */
page_size = nor - > page_size - page_offset ;
nor - > write ( nor , to , page_size , retlen , buf ) ;
/* write everything in nor->page_size chunks */
for ( i = page_size ; i < len ; i + = page_size ) {
page_size = len - i ;
if ( page_size > nor - > page_size )
page_size = nor - > page_size ;
wait_till_ready ( nor ) ;
write_enable ( nor ) ;
nor - > write ( nor , to + i , page_size , retlen , buf + i ) ;
}
}
write_err :
spi_nor_unlock_and_unprep ( nor , SPI_NOR_OPS_WRITE ) ;
return 0 ;
}
static int macronix_quad_enable ( struct spi_nor * nor )
{
int ret , val ;
val = read_sr ( nor ) ;
write_enable ( nor ) ;
nor - > cmd_buf [ 0 ] = val | SR_QUAD_EN_MX ;
2014-04-08 18:15:31 -07:00
nor - > write_reg ( nor , SPINOR_OP_WRSR , nor - > cmd_buf , 1 , 0 ) ;
2014-02-24 18:37:37 +08:00
if ( wait_till_ready ( nor ) )
return 1 ;
ret = read_sr ( nor ) ;
if ( ! ( ret > 0 & & ( ret & SR_QUAD_EN_MX ) ) ) {
dev_err ( nor - > dev , " Macronix Quad bit not set \n " ) ;
return - EINVAL ;
}
return 0 ;
}
/*
* Write status Register and configuration register with 2 bytes
* The first byte will be written to the status register , while the
* second byte will be written to the configuration register .
* Return negative if error occured .
*/
static int write_sr_cr ( struct spi_nor * nor , u16 val )
{
nor - > cmd_buf [ 0 ] = val & 0xff ;
nor - > cmd_buf [ 1 ] = ( val > > 8 ) ;
2014-04-08 18:15:31 -07:00
return nor - > write_reg ( nor , SPINOR_OP_WRSR , nor - > cmd_buf , 2 , 0 ) ;
2014-02-24 18:37:37 +08:00
}
static int spansion_quad_enable ( struct spi_nor * nor )
{
int ret ;
int quad_en = CR_QUAD_EN_SPAN < < 8 ;
write_enable ( nor ) ;
ret = write_sr_cr ( nor , quad_en ) ;
if ( ret < 0 ) {
dev_err ( nor - > dev ,
" error while writing configuration register \n " ) ;
return - EINVAL ;
}
/* read back and check it */
ret = read_cr ( nor ) ;
if ( ! ( ret > 0 & & ( ret & CR_QUAD_EN_SPAN ) ) ) {
dev_err ( nor - > dev , " Spansion Quad bit not set \n " ) ;
return - EINVAL ;
}
return 0 ;
}
static int set_quad_mode ( struct spi_nor * nor , u32 jedec_id )
{
int status ;
switch ( JEDEC_MFR ( jedec_id ) ) {
case CFI_MFR_MACRONIX :
status = macronix_quad_enable ( nor ) ;
if ( status ) {
dev_err ( nor - > dev , " Macronix quad-read not enabled \n " ) ;
return - EINVAL ;
}
return status ;
default :
status = spansion_quad_enable ( nor ) ;
if ( status ) {
dev_err ( nor - > dev , " Spansion quad-read not enabled \n " ) ;
return - EINVAL ;
}
return status ;
}
}
static int spi_nor_check ( struct spi_nor * nor )
{
if ( ! nor - > dev | | ! nor - > read | | ! nor - > write | |
! nor - > read_reg | | ! nor - > write_reg | | ! nor - > erase ) {
pr_err ( " spi-nor: please fill all the necessary fields! \n " ) ;
return - EINVAL ;
}
if ( ! nor - > read_id )
nor - > read_id = spi_nor_read_id ;
if ( ! nor - > wait_till_ready )
nor - > wait_till_ready = spi_nor_wait_till_ready ;
return 0 ;
}
int spi_nor_scan ( struct spi_nor * nor , const struct spi_device_id * id ,
enum read_mode mode )
{
struct flash_info * info ;
struct flash_platform_data * data ;
struct device * dev = nor - > dev ;
struct mtd_info * mtd = nor - > mtd ;
struct device_node * np = dev - > of_node ;
int ret ;
int i ;
ret = spi_nor_check ( nor ) ;
if ( ret )
return ret ;
/* Platform data helps sort out which chip type we have, as
* well as how this board partitions it . If we don ' t have
* a chip ID , try the JEDEC id commands ; they ' ll work for most
* newer chips , even if we don ' t recognize the particular chip .
*/
data = dev_get_platdata ( dev ) ;
if ( data & & data - > type ) {
const struct spi_device_id * plat_id ;
for ( i = 0 ; i < ARRAY_SIZE ( spi_nor_ids ) - 1 ; i + + ) {
plat_id = & spi_nor_ids [ i ] ;
if ( strcmp ( data - > type , plat_id - > name ) )
continue ;
break ;
}
if ( i < ARRAY_SIZE ( spi_nor_ids ) - 1 )
id = plat_id ;
else
dev_warn ( dev , " unrecognized id %s \n " , data - > type ) ;
}
info = ( void * ) id - > driver_data ;
if ( info - > jedec_id ) {
const struct spi_device_id * jid ;
jid = jedec_probe ( nor ) ;
if ( IS_ERR ( jid ) ) {
return PTR_ERR ( jid ) ;
} else if ( jid ! = id ) {
/*
* JEDEC knows better , so overwrite platform ID . We
* can ' t trust partitions any longer , but we ' ll let
* mtd apply them anyway , since some partitions may be
* marked read - only , and we don ' t want to lose that
* information , even if it ' s not 100 % accurate .
*/
dev_warn ( dev , " found %s, expected %s \n " ,
jid - > name , id - > name ) ;
id = jid ;
info = ( void * ) jid - > driver_data ;
}
}
mutex_init ( & nor - > lock ) ;
/*
* Atmel , SST and Intel / Numonyx serial nor tend to power
* up with the software protection bits set
*/
if ( JEDEC_MFR ( info - > jedec_id ) = = CFI_MFR_ATMEL | |
JEDEC_MFR ( info - > jedec_id ) = = CFI_MFR_INTEL | |
JEDEC_MFR ( info - > jedec_id ) = = CFI_MFR_SST ) {
write_enable ( nor ) ;
write_sr ( nor , 0 ) ;
}
if ( data & & data - > name )
mtd - > name = data - > name ;
else
mtd - > name = dev_name ( dev ) ;
mtd - > type = MTD_NORFLASH ;
mtd - > writesize = 1 ;
mtd - > flags = MTD_CAP_NORFLASH ;
mtd - > size = info - > sector_size * info - > n_sectors ;
mtd - > _erase = spi_nor_erase ;
mtd - > _read = spi_nor_read ;
/* nor protection support for STmicro chips */
if ( JEDEC_MFR ( info - > jedec_id ) = = CFI_MFR_ST ) {
mtd - > _lock = spi_nor_lock ;
mtd - > _unlock = spi_nor_unlock ;
}
/* sst nor chips use AAI word program */
if ( info - > flags & SST_WRITE )
mtd - > _write = sst_write ;
else
mtd - > _write = spi_nor_write ;
/* prefer "small sector" erase if possible */
if ( info - > flags & SECT_4K ) {
2014-04-08 18:15:31 -07:00
nor - > erase_opcode = SPINOR_OP_BE_4K ;
2014-02-24 18:37:37 +08:00
mtd - > erasesize = 4096 ;
} else if ( info - > flags & SECT_4K_PMC ) {
2014-04-08 18:15:31 -07:00
nor - > erase_opcode = SPINOR_OP_BE_4K_PMC ;
2014-02-24 18:37:37 +08:00
mtd - > erasesize = 4096 ;
} else {
2014-04-08 18:15:31 -07:00
nor - > erase_opcode = SPINOR_OP_SE ;
2014-02-24 18:37:37 +08:00
mtd - > erasesize = info - > sector_size ;
}
if ( info - > flags & SPI_NOR_NO_ERASE )
mtd - > flags | = MTD_NO_ERASE ;
mtd - > dev . parent = dev ;
nor - > page_size = info - > page_size ;
mtd - > writebufsize = nor - > page_size ;
if ( np ) {
/* If we were instantiated by DT, use it */
if ( of_property_read_bool ( np , " m25p,fast-read " ) )
nor - > flash_read = SPI_NOR_FAST ;
else
nor - > flash_read = SPI_NOR_NORMAL ;
} else {
/* If we weren't instantiated by DT, default to fast-read */
nor - > flash_read = SPI_NOR_FAST ;
}
/* Some devices cannot do fast-read, no matter what DT tells us */
if ( info - > flags & SPI_NOR_NO_FR )
nor - > flash_read = SPI_NOR_NORMAL ;
/* Quad/Dual-read mode takes precedence over fast/normal */
if ( mode = = SPI_NOR_QUAD & & info - > flags & SPI_NOR_QUAD_READ ) {
ret = set_quad_mode ( nor , info - > jedec_id ) ;
if ( ret ) {
dev_err ( dev , " quad mode not supported \n " ) ;
return ret ;
}
nor - > flash_read = SPI_NOR_QUAD ;
} else if ( mode = = SPI_NOR_DUAL & & info - > flags & SPI_NOR_DUAL_READ ) {
nor - > flash_read = SPI_NOR_DUAL ;
}
/* Default commands */
switch ( nor - > flash_read ) {
case SPI_NOR_QUAD :
2014-04-08 18:15:31 -07:00
nor - > read_opcode = SPINOR_OP_QUAD_READ ;
2014-02-24 18:37:37 +08:00
break ;
case SPI_NOR_DUAL :
2014-04-08 18:15:31 -07:00
nor - > read_opcode = SPINOR_OP_DUAL_READ ;
2014-02-24 18:37:37 +08:00
break ;
case SPI_NOR_FAST :
2014-04-08 18:15:31 -07:00
nor - > read_opcode = SPINOR_OP_FAST_READ ;
2014-02-24 18:37:37 +08:00
break ;
case SPI_NOR_NORMAL :
2014-04-08 18:15:31 -07:00
nor - > read_opcode = SPINOR_OP_NORM_READ ;
2014-02-24 18:37:37 +08:00
break ;
default :
dev_err ( dev , " No Read opcode defined \n " ) ;
return - EINVAL ;
}
2014-04-08 18:15:31 -07:00
nor - > program_opcode = SPINOR_OP_PP ;
2014-02-24 18:37:37 +08:00
if ( info - > addr_width )
nor - > addr_width = info - > addr_width ;
else if ( mtd - > size > 0x1000000 ) {
/* enable 4-byte addressing if the device exceeds 16MiB */
nor - > addr_width = 4 ;
if ( JEDEC_MFR ( info - > jedec_id ) = = CFI_MFR_AMD ) {
/* Dedicated 4-byte command set */
switch ( nor - > flash_read ) {
case SPI_NOR_QUAD :
2014-04-08 18:15:31 -07:00
nor - > read_opcode = SPINOR_OP_QUAD_READ_4B ;
2014-02-24 18:37:37 +08:00
break ;
case SPI_NOR_DUAL :
2014-04-08 18:15:31 -07:00
nor - > read_opcode = SPINOR_OP_DUAL_READ_4B ;
2014-02-24 18:37:37 +08:00
break ;
case SPI_NOR_FAST :
2014-04-08 18:15:31 -07:00
nor - > read_opcode = SPINOR_OP_FAST_READ_4B ;
2014-02-24 18:37:37 +08:00
break ;
case SPI_NOR_NORMAL :
2014-04-08 18:15:31 -07:00
nor - > read_opcode = SPINOR_OP_NORM_READ_4B ;
2014-02-24 18:37:37 +08:00
break ;
}
2014-04-08 18:15:31 -07:00
nor - > program_opcode = SPINOR_OP_PP_4B ;
2014-02-24 18:37:37 +08:00
/* No small sector erase for 4-byte command set */
2014-04-08 18:15:31 -07:00
nor - > erase_opcode = SPINOR_OP_SE_4B ;
2014-02-24 18:37:37 +08:00
mtd - > erasesize = info - > sector_size ;
} else
set_4byte ( nor , info - > jedec_id , 1 ) ;
} else {
nor - > addr_width = 3 ;
}
nor - > read_dummy = spi_nor_read_dummy_cycles ( nor ) ;
dev_info ( dev , " %s (%lld Kbytes) \n " , id - > name ,
( long long ) mtd - > size > > 10 ) ;
dev_dbg ( dev ,
" mtd .name = %s, .size = 0x%llx (%lldMiB), "
" .erasesize = 0x%.8x (%uKiB) .numeraseregions = %d \n " ,
mtd - > name , ( long long ) mtd - > size , ( long long ) ( mtd - > size > > 20 ) ,
mtd - > erasesize , mtd - > erasesize / 1024 , mtd - > numeraseregions ) ;
if ( mtd - > numeraseregions )
for ( i = 0 ; i < mtd - > numeraseregions ; i + + )
dev_dbg ( dev ,
" mtd.eraseregions[%d] = { .offset = 0x%llx, "
" .erasesize = 0x%.8x (%uKiB), "
" .numblocks = %d } \n " ,
i , ( long long ) mtd - > eraseregions [ i ] . offset ,
mtd - > eraseregions [ i ] . erasesize ,
mtd - > eraseregions [ i ] . erasesize / 1024 ,
mtd - > eraseregions [ i ] . numblocks ) ;
return 0 ;
}
2014-04-08 18:22:57 -07:00
EXPORT_SYMBOL_GPL ( spi_nor_scan ) ;
2014-02-24 18:37:37 +08:00
2014-02-24 18:37:40 +08:00
const struct spi_device_id * spi_nor_match_id ( char * name )
{
const struct spi_device_id * id = spi_nor_ids ;
while ( id - > name [ 0 ] ) {
if ( ! strcmp ( name , id - > name ) )
return id ;
id + + ;
}
return NULL ;
}
2014-04-08 18:22:57 -07:00
EXPORT_SYMBOL_GPL ( spi_nor_match_id ) ;
2014-02-24 18:37:40 +08:00
2014-02-24 18:37:37 +08:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Huang Shijie <shijie8@gmail.com> " ) ;
MODULE_AUTHOR ( " Mike Lavender " ) ;
MODULE_DESCRIPTION ( " framework for SPI NOR " ) ;