2007-02-12 11:52:48 +03:00
/*
* at25 . c - - support most SPI EEPROMs , such as Atmel AT25 models
*
* Copyright ( C ) 2006 David Brownell
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/sched.h>
# include <linux/spi/spi.h>
# include <linux/spi/eeprom.h>
struct at25_data {
struct spi_device * spi ;
struct mutex lock ;
struct spi_eeprom chip ;
struct bin_attribute bin ;
unsigned addrlen ;
} ;
# define AT25_WREN 0x06 /* latch the write enable */
# define AT25_WRDI 0x04 /* reset the write enable */
# define AT25_RDSR 0x05 /* read status register */
# define AT25_WRSR 0x01 /* write status register */
# define AT25_READ 0x03 /* read byte(s) */
# define AT25_WRITE 0x02 /* write byte(s)/sector */
# define AT25_SR_nRDY 0x01 /* nRDY = write-in-progress */
# define AT25_SR_WEN 0x02 /* write enable (latched) */
# define AT25_SR_BP0 0x04 /* BP for software writeprotect */
# define AT25_SR_BP1 0x08
# define AT25_SR_WPEN 0x80 /* writeprotect enable */
# define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */
/* Specs often allow 5 msec for a page write, sometimes 20 msec;
* it ' s important to recover from write timeouts .
*/
# define EE_TIMEOUT 25
/*-------------------------------------------------------------------------*/
# define io_limit PAGE_SIZE /* bytes */
static ssize_t
at25_ee_read (
struct at25_data * at25 ,
char * buf ,
unsigned offset ,
size_t count
)
{
u8 command [ EE_MAXADDRLEN + 1 ] ;
u8 * cp ;
ssize_t status ;
struct spi_transfer t [ 2 ] ;
struct spi_message m ;
cp = command ;
* cp + + = AT25_READ ;
/* 8/16/24-bit address is written MSB first */
switch ( at25 - > addrlen ) {
default : /* case 3 */
* cp + + = offset > > 16 ;
case 2 :
* cp + + = offset > > 8 ;
case 1 :
case 0 : /* can't happen: for better codegen */
* cp + + = offset > > 0 ;
}
spi_message_init ( & m ) ;
memset ( t , 0 , sizeof t ) ;
t [ 0 ] . tx_buf = command ;
t [ 0 ] . len = at25 - > addrlen + 1 ;
spi_message_add_tail ( & t [ 0 ] , & m ) ;
t [ 1 ] . rx_buf = buf ;
t [ 1 ] . len = count ;
spi_message_add_tail ( & t [ 1 ] , & m ) ;
mutex_lock ( & at25 - > lock ) ;
/* Read it all at once.
*
* REVISIT that ' s potentially a problem with large chips , if
* other devices on the bus need to be accessed regularly or
* this chip is clocked very slowly
*/
status = spi_sync ( at25 - > spi , & m ) ;
dev_dbg ( & at25 - > spi - > dev ,
" read %Zd bytes at %d --> %d \n " ,
count , offset , ( int ) status ) ;
mutex_unlock ( & at25 - > lock ) ;
return status ? status : count ;
}
static ssize_t
at25_bin_read ( struct kobject * kobj , char * buf , loff_t off , size_t count )
{
struct device * dev ;
struct at25_data * at25 ;
dev = container_of ( kobj , struct device , kobj ) ;
at25 = dev_get_drvdata ( dev ) ;
if ( unlikely ( off > = at25 - > bin . size ) )
return 0 ;
if ( ( off + count ) > at25 - > bin . size )
count = at25 - > bin . size - off ;
if ( unlikely ( ! count ) )
return count ;
return at25_ee_read ( at25 , buf , off , count ) ;
}
static ssize_t
at25_ee_write ( struct at25_data * at25 , char * buf , loff_t off , size_t count )
{
ssize_t status = 0 ;
unsigned written = 0 ;
unsigned buf_size ;
u8 * bounce ;
/* Temp buffer starts with command and address */
buf_size = at25 - > chip . page_size ;
if ( buf_size > io_limit )
buf_size = io_limit ;
bounce = kmalloc ( buf_size + at25 - > addrlen + 1 , GFP_KERNEL ) ;
if ( ! bounce )
return - ENOMEM ;
/* For write, rollover is within the page ... so we write at
* most one page , then manually roll over to the next page .
*/
bounce [ 0 ] = AT25_WRITE ;
mutex_lock ( & at25 - > lock ) ;
do {
unsigned long timeout , retries ;
unsigned segment ;
unsigned offset = ( unsigned ) off ;
u8 * cp = bounce + 1 ;
* cp = AT25_WREN ;
status = spi_write ( at25 - > spi , cp , 1 ) ;
if ( status < 0 ) {
dev_dbg ( & at25 - > spi - > dev , " WREN --> %d \n " ,
( int ) status ) ;
break ;
}
/* 8/16/24-bit address is written MSB first */
switch ( at25 - > addrlen ) {
default : /* case 3 */
* cp + + = offset > > 16 ;
case 2 :
* cp + + = offset > > 8 ;
case 1 :
case 0 : /* can't happen: for better codegen */
* cp + + = offset > > 0 ;
}
/* Write as much of a page as we can */
segment = buf_size - ( offset % buf_size ) ;
if ( segment > count )
segment = count ;
memcpy ( cp , buf , segment ) ;
status = spi_write ( at25 - > spi , bounce ,
segment + at25 - > addrlen + 1 ) ;
dev_dbg ( & at25 - > spi - > dev ,
" write %u bytes at %u --> %d \n " ,
segment , offset , ( int ) status ) ;
if ( status < 0 )
break ;
/* REVISIT this should detect (or prevent) failed writes
* to readonly sections of the EEPROM . . .
*/
/* Wait for non-busy status */
timeout = jiffies + msecs_to_jiffies ( EE_TIMEOUT ) ;
retries = 0 ;
do {
int sr ;
sr = spi_w8r8 ( at25 - > spi , AT25_RDSR ) ;
if ( sr < 0 | | ( sr & AT25_SR_nRDY ) ) {
dev_dbg ( & at25 - > spi - > dev ,
" rdsr --> %d (%02x) \n " , sr , sr ) ;
/* at HZ=100, this is sloooow */
msleep ( 1 ) ;
continue ;
}
if ( ! ( sr & AT25_SR_nRDY ) )
break ;
} while ( retries + + < 3 | | time_before_eq ( jiffies , timeout ) ) ;
if ( time_after ( jiffies , timeout ) ) {
dev_err ( & at25 - > spi - > dev ,
" write %d bytes offset %d, "
" timeout after %u msecs \n " ,
segment , offset ,
jiffies_to_msecs ( jiffies -
( timeout - EE_TIMEOUT ) ) ) ;
status = - ETIMEDOUT ;
break ;
}
off + = segment ;
buf + = segment ;
count - = segment ;
written + = segment ;
} while ( count > 0 ) ;
mutex_unlock ( & at25 - > lock ) ;
kfree ( bounce ) ;
return written ? written : status ;
}
static ssize_t
at25_bin_write ( struct kobject * kobj , char * buf , loff_t off , size_t count )
{
struct device * dev ;
struct at25_data * at25 ;
dev = container_of ( kobj , struct device , kobj ) ;
at25 = dev_get_drvdata ( dev ) ;
if ( unlikely ( off > = at25 - > bin . size ) )
return - EFBIG ;
if ( ( off + count ) > at25 - > bin . size )
count = at25 - > bin . size - off ;
if ( unlikely ( ! count ) )
return count ;
return at25_ee_write ( at25 , buf , off , count ) ;
}
/*-------------------------------------------------------------------------*/
static int at25_probe ( struct spi_device * spi )
{
struct at25_data * at25 = NULL ;
const struct spi_eeprom * chip ;
int err ;
int sr ;
int addrlen ;
/* Chip description */
chip = spi - > dev . platform_data ;
if ( ! chip ) {
dev_dbg ( & spi - > dev , " no chip description \n " ) ;
err = - ENODEV ;
goto fail ;
}
/* For now we only support 8/16/24 bit addressing */
if ( chip - > flags & EE_ADDR1 )
addrlen = 1 ;
else if ( chip - > flags & EE_ADDR2 )
addrlen = 2 ;
else if ( chip - > flags & EE_ADDR3 )
addrlen = 3 ;
else {
dev_dbg ( & spi - > dev , " unsupported address type \n " ) ;
err = - EINVAL ;
goto fail ;
}
/* Ping the chip ... the status register is pretty portable,
* unlike probing manufacturer IDs . We do expect that system
* firmware didn ' t write it in the past few milliseconds !
*/
sr = spi_w8r8 ( spi , AT25_RDSR ) ;
if ( sr < 0 | | sr & AT25_SR_nRDY ) {
2007-03-17 00:38:20 +03:00
dev_dbg ( & spi - > dev , " rdsr --> %d (%02x) \n " , sr , sr ) ;
2007-02-12 11:52:48 +03:00
err = - ENXIO ;
goto fail ;
}
if ( ! ( at25 = kzalloc ( sizeof * at25 , GFP_KERNEL ) ) ) {
err = - ENOMEM ;
goto fail ;
}
mutex_init ( & at25 - > lock ) ;
at25 - > chip = * chip ;
at25 - > spi = spi_dev_get ( spi ) ;
dev_set_drvdata ( & spi - > dev , at25 ) ;
at25 - > addrlen = addrlen ;
/* Export the EEPROM bytes through sysfs, since that's convenient.
* Default to root - only access to the data ; EEPROMs often hold data
* that ' s sensitive for read and / or write , like ethernet addresses ,
* security codes , board - specific manufacturing calibrations , etc .
*/
at25 - > bin . attr . name = " eeprom " ;
at25 - > bin . attr . mode = S_IRUSR ;
at25 - > bin . attr . owner = THIS_MODULE ;
at25 - > bin . read = at25_bin_read ;
at25 - > bin . size = at25 - > chip . byte_len ;
if ( ! ( chip - > flags & EE_READONLY ) ) {
at25 - > bin . write = at25_bin_write ;
at25 - > bin . attr . mode | = S_IWUSR ;
}
err = sysfs_create_bin_file ( & spi - > dev . kobj , & at25 - > bin ) ;
if ( err )
goto fail ;
dev_info ( & spi - > dev , " %Zd %s %s eeprom%s, pagesize %u \n " ,
( at25 - > bin . size < 1024 )
? at25 - > bin . size
: ( at25 - > bin . size / 1024 ) ,
( at25 - > bin . size < 1024 ) ? " Byte " : " KByte " ,
at25 - > chip . name ,
( chip - > flags & EE_READONLY ) ? " (readonly) " : " " ,
at25 - > chip . page_size ) ;
return 0 ;
fail :
dev_dbg ( & spi - > dev , " probe err %d \n " , err ) ;
kfree ( at25 ) ;
return err ;
}
static int __devexit at25_remove ( struct spi_device * spi )
{
struct at25_data * at25 ;
at25 = dev_get_drvdata ( & spi - > dev ) ;
sysfs_remove_bin_file ( & spi - > dev . kobj , & at25 - > bin ) ;
kfree ( at25 ) ;
return 0 ;
}
/*-------------------------------------------------------------------------*/
static struct spi_driver at25_driver = {
. driver = {
. name = " at25 " ,
. owner = THIS_MODULE ,
} ,
. probe = at25_probe ,
. remove = __devexit_p ( at25_remove ) ,
} ;
static int __init at25_init ( void )
{
return spi_register_driver ( & at25_driver ) ;
}
module_init ( at25_init ) ;
static void __exit at25_exit ( void )
{
spi_unregister_driver ( & at25_driver ) ;
}
module_exit ( at25_exit ) ;
MODULE_DESCRIPTION ( " Driver for most SPI EEPROMs " ) ;
MODULE_AUTHOR ( " David Brownell " ) ;
MODULE_LICENSE ( " GPL " ) ;