2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2007-02-12 11:52:48 +03:00
/*
* at25 . c - - support most SPI EEPROMs , such as Atmel AT25 models
2021-06-11 12:45:58 +03:00
* and Cypress FRAMs FM25 models
2007-02-12 11:52:48 +03:00
*
* Copyright ( C ) 2006 David Brownell
*/
2021-11-26 00:31:59 +03:00
# include <linux/bits.h>
2007-02-12 11:52:48 +03:00
# include <linux/delay.h>
# include <linux/device.h>
2021-11-26 00:32:01 +03:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/property.h>
2007-02-12 11:52:48 +03:00
# include <linux/sched.h>
2021-11-26 00:32:01 +03:00
# include <linux/slab.h>
2007-02-12 11:52:48 +03:00
# include <linux/spi/eeprom.h>
2021-11-26 00:32:01 +03:00
# include <linux/spi/spi.h>
# include <linux/nvmem-provider.h>
2007-02-12 11:52:48 +03:00
2007-12-05 10:45:10 +03:00
/*
* NOTE : this is an * EEPROM * driver . The vagaries of product naming
* mean that some AT25 products are EEPROMs , and others are FLASH .
* Handle FLASH chips with the drivers / mtd / devices / m25p80 . c driver ,
* not this one !
2020-11-07 16:33:35 +03:00
*
* EEPROMs that can be used with this driver include , for example :
* AT25M02 , AT25128B
2007-12-05 10:45:10 +03:00
*/
2021-06-11 12:45:58 +03:00
# define FM25_SN_LEN 8 /* serial number length */
2007-02-12 11:52:48 +03:00
struct at25_data {
2021-11-26 00:32:00 +03:00
struct spi_eeprom chip ;
2007-02-12 11:52:48 +03:00
struct spi_device * spi ;
struct mutex lock ;
unsigned addrlen ;
2016-02-26 22:59:22 +03:00
struct nvmem_config nvmem_config ;
struct nvmem_device * nvmem ;
2021-06-11 12:45:58 +03:00
u8 sernum [ FM25_SN_LEN ] ;
2007-02-12 11:52:48 +03:00
} ;
# 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 */
2021-06-11 12:45:58 +03:00
# define FM25_SLEEP 0xb9 /* enter sleep mode */
# define FM25_RDID 0x9f /* read device ID */
# define FM25_RDSN 0xc3 /* read S/N */
2007-02-12 11:52:48 +03:00
# 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 */
2012-04-18 10:29:34 +04:00
# define AT25_INSTR_BIT3 0x08 /* Additional address bit in instr */
2007-02-12 11:52:48 +03:00
2021-06-11 12:45:58 +03:00
# define FM25_ID_LEN 9 /* ID length */
2007-02-12 11:52:48 +03:00
# 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 */
2016-04-24 22:28:07 +03:00
static int at25_ee_read ( void * priv , unsigned int offset ,
void * val , size_t count )
2007-02-12 11:52:48 +03:00
{
2016-04-24 22:28:07 +03:00
struct at25_data * at25 = priv ;
char * buf = val ;
2007-02-12 11:52:48 +03:00
u8 command [ EE_MAXADDRLEN + 1 ] ;
u8 * cp ;
ssize_t status ;
struct spi_transfer t [ 2 ] ;
struct spi_message m ;
2012-04-18 10:29:34 +04:00
u8 instr ;
2007-02-12 11:52:48 +03:00
2016-02-26 22:59:22 +03:00
if ( unlikely ( offset > = at25 - > chip . byte_len ) )
2016-04-24 22:28:07 +03:00
return - EINVAL ;
2016-02-26 22:59:22 +03:00
if ( ( offset + count ) > at25 - > chip . byte_len )
count = at25 - > chip . byte_len - offset ;
2009-04-03 03:56:58 +04:00
if ( unlikely ( ! count ) )
2016-04-24 22:28:07 +03:00
return - EINVAL ;
2009-04-03 03:56:58 +04:00
2007-02-12 11:52:48 +03:00
cp = command ;
2012-04-18 10:29:34 +04:00
instr = AT25_READ ;
if ( at25 - > chip . flags & EE_INSTR_BIT3_IS_ADDR )
2021-11-26 00:31:59 +03:00
if ( offset > = BIT ( at25 - > addrlen * 8 ) )
2012-04-18 10:29:34 +04:00
instr | = AT25_INSTR_BIT3 ;
* cp + + = instr ;
2007-02-12 11:52:48 +03:00
/* 8/16/24-bit address is written MSB first */
switch ( at25 - > addrlen ) {
default : /* case 3 */
* cp + + = offset > > 16 ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2007-02-12 11:52:48 +03:00
case 2 :
* cp + + = offset > > 8 ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2007-02-12 11:52:48 +03:00
case 1 :
case 0 : /* can't happen: for better codegen */
* cp + + = offset > > 0 ;
}
spi_message_init ( & m ) ;
2018-03-21 08:34:35 +03:00
memset ( t , 0 , sizeof ( t ) ) ;
2007-02-12 11:52:48 +03:00
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 ) ;
2016-09-11 14:58:26 +03:00
dev_dbg ( & at25 - > spi - > dev , " read %zu bytes at %d --> %zd \n " ,
count , offset , status ) ;
2007-02-12 11:52:48 +03:00
mutex_unlock ( & at25 - > lock ) ;
2016-04-24 22:28:07 +03:00
return status ;
2007-02-12 11:52:48 +03:00
}
2021-06-11 12:45:58 +03:00
/*
* read extra registers as ID or serial number
*/
static int fm25_aux_read ( struct at25_data * at25 , u8 * buf , uint8_t command ,
int len )
{
int status ;
struct spi_transfer t [ 2 ] ;
struct spi_message m ;
spi_message_init ( & m ) ;
memset ( t , 0 , sizeof ( t ) ) ;
t [ 0 ] . tx_buf = & command ;
t [ 0 ] . len = 1 ;
spi_message_add_tail ( & t [ 0 ] , & m ) ;
t [ 1 ] . rx_buf = buf ;
t [ 1 ] . len = len ;
spi_message_add_tail ( & t [ 1 ] , & m ) ;
mutex_lock ( & at25 - > lock ) ;
status = spi_sync ( at25 - > spi , & m ) ;
dev_dbg ( & at25 - > spi - > dev , " read %d aux bytes --> %d \n " , len , status ) ;
mutex_unlock ( & at25 - > lock ) ;
return status ;
}
static ssize_t sernum_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct at25_data * at25 ;
at25 = dev_get_drvdata ( dev ) ;
2021-06-11 17:27:06 +03:00
return sysfs_emit ( buf , " %*ph \n " , ( int ) sizeof ( at25 - > sernum ) , at25 - > sernum ) ;
2021-06-11 12:45:58 +03:00
}
static DEVICE_ATTR_RO ( sernum ) ;
static struct attribute * sernum_attrs [ ] = {
& dev_attr_sernum . attr ,
NULL ,
} ;
ATTRIBUTE_GROUPS ( sernum ) ;
2016-04-24 22:28:07 +03:00
static int at25_ee_write ( void * priv , unsigned int off , void * val , size_t count )
2007-02-12 11:52:48 +03:00
{
2016-04-24 22:28:07 +03:00
struct at25_data * at25 = priv ;
const char * buf = val ;
int status = 0 ;
2007-02-12 11:52:48 +03:00
unsigned buf_size ;
u8 * bounce ;
2016-02-26 22:59:22 +03:00
if ( unlikely ( off > = at25 - > chip . byte_len ) )
2009-04-03 03:56:58 +04:00
return - EFBIG ;
2016-02-26 22:59:22 +03:00
if ( ( off + count ) > at25 - > chip . byte_len )
count = at25 - > chip . byte_len - off ;
2009-04-03 03:56:58 +04:00
if ( unlikely ( ! count ) )
2016-04-24 22:28:07 +03:00
return - EINVAL ;
2009-04-03 03:56:58 +04:00
2007-02-12 11:52:48 +03:00
/* 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 .
*/
mutex_lock ( & at25 - > lock ) ;
do {
unsigned long timeout , retries ;
unsigned segment ;
unsigned offset = ( unsigned ) off ;
2012-04-18 10:29:34 +04:00
u8 * cp = bounce ;
2009-07-30 02:04:05 +04:00
int sr ;
2012-04-18 10:29:34 +04:00
u8 instr ;
2007-02-12 11:52:48 +03:00
* cp = AT25_WREN ;
status = spi_write ( at25 - > spi , cp , 1 ) ;
if ( status < 0 ) {
2016-09-11 14:58:26 +03:00
dev_dbg ( & at25 - > spi - > dev , " WREN --> %d \n " , status ) ;
2007-02-12 11:52:48 +03:00
break ;
}
2012-04-18 10:29:34 +04:00
instr = AT25_WRITE ;
if ( at25 - > chip . flags & EE_INSTR_BIT3_IS_ADDR )
2021-11-26 00:31:59 +03:00
if ( offset > = BIT ( at25 - > addrlen * 8 ) )
2012-04-18 10:29:34 +04:00
instr | = AT25_INSTR_BIT3 ;
* cp + + = instr ;
2007-02-12 11:52:48 +03:00
/* 8/16/24-bit address is written MSB first */
switch ( at25 - > addrlen ) {
default : /* case 3 */
* cp + + = offset > > 16 ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2007-02-12 11:52:48 +03:00
case 2 :
* cp + + = offset > > 8 ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2007-02-12 11:52:48 +03:00
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 ) ;
2016-09-11 14:58:26 +03:00
dev_dbg ( & at25 - > spi - > dev , " write %u bytes at %u --> %d \n " ,
segment , offset , status ) ;
2007-02-12 11:52:48 +03:00
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 {
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 ) ) ;
2009-07-30 02:04:05 +04:00
if ( ( sr < 0 ) | | ( sr & AT25_SR_nRDY ) ) {
2007-02-12 11:52:48 +03:00
dev_err ( & at25 - > spi - > dev ,
2016-09-11 14:58:26 +03:00
" write %u bytes offset %u, timeout after %u msecs \n " ,
2007-02-12 11:52:48 +03:00
segment , offset ,
jiffies_to_msecs ( jiffies -
( timeout - EE_TIMEOUT ) ) ) ;
status = - ETIMEDOUT ;
break ;
}
off + = segment ;
buf + = segment ;
count - = segment ;
} while ( count > 0 ) ;
mutex_unlock ( & at25 - > lock ) ;
kfree ( bounce ) ;
2016-04-24 22:28:07 +03:00
return status ;
2007-02-12 11:52:48 +03:00
}
/*-------------------------------------------------------------------------*/
2014-10-21 15:33:56 +04:00
static int at25_fw_to_chip ( struct device * dev , struct spi_eeprom * chip )
2012-08-22 23:03:57 +04:00
{
u32 val ;
2021-11-26 00:31:55 +03:00
int err ;
2012-08-22 23:03:57 +04:00
2014-10-21 15:33:56 +04:00
strncpy ( chip - > name , " at25 " , sizeof ( chip - > name ) ) ;
2012-08-22 23:03:57 +04:00
2021-11-26 00:31:55 +03:00
err = device_property_read_u32 ( dev , " size " , & val ) ;
if ( err )
err = device_property_read_u32 ( dev , " at25,byte-len " , & val ) ;
if ( err ) {
2012-08-22 23:03:57 +04:00
dev_err ( dev , " Error: missing \" size \" property \n " ) ;
2021-11-26 00:31:55 +03:00
return err ;
2012-08-22 23:03:57 +04:00
}
2021-11-26 00:31:55 +03:00
chip - > byte_len = val ;
2012-08-22 23:03:57 +04:00
2021-11-26 00:31:55 +03:00
err = device_property_read_u32 ( dev , " pagesize " , & val ) ;
if ( err )
err = device_property_read_u32 ( dev , " at25,page-size " , & val ) ;
if ( err ) {
2012-08-22 23:03:57 +04:00
dev_err ( dev , " Error: missing \" pagesize \" property \n " ) ;
2021-11-26 00:31:55 +03:00
return err ;
2012-08-22 23:03:57 +04:00
}
2021-11-26 00:31:55 +03:00
chip - > page_size = val ;
2021-11-26 00:31:56 +03:00
err = device_property_read_u32 ( dev , " address-width " , & val ) ;
2021-11-26 00:31:55 +03:00
if ( err ) {
2021-11-26 00:31:56 +03:00
err = device_property_read_u32 ( dev , " at25,addr-mode " , & val ) ;
2021-11-26 00:31:55 +03:00
if ( err ) {
dev_err ( dev , " Error: missing \" address-width \" property \n " ) ;
return err ;
2012-08-22 23:03:57 +04:00
}
2021-11-26 00:31:56 +03:00
chip - > flags = ( u16 ) val ;
} else {
2012-08-22 23:03:57 +04:00
switch ( val ) {
2017-12-08 16:46:41 +03:00
case 9 :
chip - > flags | = EE_INSTR_BIT3_IS_ADDR ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2012-08-22 23:03:57 +04:00
case 8 :
chip - > flags | = EE_ADDR1 ;
break ;
case 16 :
chip - > flags | = EE_ADDR2 ;
break ;
case 24 :
chip - > flags | = EE_ADDR3 ;
break ;
default :
dev_err ( dev ,
" Error: bad \" address-width \" property: %u \n " ,
val ) ;
return - ENODEV ;
}
2014-10-21 15:33:56 +04:00
if ( device_property_present ( dev , " read-only " ) )
2012-08-22 23:03:57 +04:00
chip - > flags | = EE_READONLY ;
}
return 0 ;
}
2021-11-26 00:32:00 +03:00
static int at25_fram_to_chip ( struct device * dev , struct spi_eeprom * chip )
{
struct at25_data * at25 = container_of ( chip , struct at25_data , chip ) ;
u8 sernum [ FM25_SN_LEN ] ;
u8 id [ FM25_ID_LEN ] ;
int i ;
strncpy ( chip - > name , " fm25 " , sizeof ( chip - > name ) ) ;
/* Get ID of chip */
fm25_aux_read ( at25 , id , FM25_RDID , FM25_ID_LEN ) ;
if ( id [ 6 ] ! = 0xc2 ) {
dev_err ( dev , " Error: no Cypress FRAM (id %02x) \n " , id [ 6 ] ) ;
return - ENODEV ;
}
/* Set size found in ID */
if ( id [ 7 ] < 0x21 | | id [ 7 ] > 0x26 ) {
dev_err ( dev , " Error: unsupported size (id %02x) \n " , id [ 7 ] ) ;
return - ENODEV ;
}
chip - > byte_len = BIT ( id [ 7 ] - 0x21 + 4 ) * 1024 ;
if ( chip - > byte_len > 64 * 1024 )
chip - > flags | = EE_ADDR3 ;
else
chip - > flags | = EE_ADDR2 ;
if ( id [ 8 ] ) {
fm25_aux_read ( at25 , sernum , FM25_RDSN , FM25_SN_LEN ) ;
/* Swap byte order */
for ( i = 0 ; i < FM25_SN_LEN ; i + + )
at25 - > sernum [ i ] = sernum [ FM25_SN_LEN - 1 - i ] ;
}
chip - > page_size = PAGE_SIZE ;
return 0 ;
}
2021-06-11 12:45:58 +03:00
static const struct of_device_id at25_of_match [ ] = {
2021-06-11 18:24:16 +03:00
{ . compatible = " atmel,at25 " , } ,
{ . compatible = " cypress,fm25 " , } ,
2021-06-11 12:45:58 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , at25_of_match ) ;
2021-09-23 20:24:53 +03:00
static const struct spi_device_id at25_spi_ids [ ] = {
{ . name = " at25 " , } ,
{ . name = " fm25 " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( spi , at25_spi_ids ) ;
2007-02-12 11:52:48 +03:00
static int at25_probe ( struct spi_device * spi )
{
struct at25_data * at25 = NULL ;
int err ;
int sr ;
2021-11-26 00:31:58 +03:00
struct spi_eeprom * pdata ;
2021-11-26 00:27:27 +03:00
bool is_fram ;
2021-06-11 12:45:58 +03:00
2021-11-26 00:27:27 +03:00
err = device_property_match_string ( & spi - > dev , " compatible " , " cypress,fm25 " ) ;
if ( err > = 0 )
is_fram = true ;
else
is_fram = false ;
2007-02-12 11:52:48 +03:00
/* 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 ) ;
2013-05-29 00:01:21 +04:00
return - ENXIO ;
2007-02-12 11:52:48 +03:00
}
2013-05-29 00:01:21 +04:00
at25 = devm_kzalloc ( & spi - > dev , sizeof ( struct at25_data ) , GFP_KERNEL ) ;
if ( ! at25 )
return - ENOMEM ;
2007-02-12 11:52:48 +03:00
mutex_init ( & at25 - > lock ) ;
2016-04-20 12:16:35 +03:00
at25 - > spi = spi ;
2013-04-05 05:55:35 +04:00
spi_set_drvdata ( spi , at25 ) ;
2007-02-12 11:52:48 +03:00
2021-11-26 00:31:58 +03:00
/* Chip description */
pdata = dev_get_platdata ( & spi - > dev ) ;
if ( pdata ) {
at25 - > chip = * pdata ;
} else {
2021-11-26 00:32:00 +03:00
if ( is_fram )
err = at25_fram_to_chip ( & spi - > dev , & at25 - > chip ) ;
2021-06-11 12:45:58 +03:00
else
2021-11-26 00:32:00 +03:00
err = at25_fw_to_chip ( & spi - > dev , & at25 - > chip ) ;
if ( err )
return err ;
2021-06-11 12:45:58 +03:00
}
/* For now we only support 8/16/24 bit addressing */
if ( at25 - > chip . flags & EE_ADDR1 )
at25 - > addrlen = 1 ;
else if ( at25 - > chip . flags & EE_ADDR2 )
at25 - > addrlen = 2 ;
else if ( at25 - > chip . flags & EE_ADDR3 )
at25 - > addrlen = 3 ;
else {
dev_dbg ( & spi - > dev , " unsupported address type \n " ) ;
return - EINVAL ;
}
at25 - > nvmem_config . type = is_fram ? NVMEM_TYPE_FRAM : NVMEM_TYPE_EEPROM ;
2016-02-26 22:59:22 +03:00
at25 - > nvmem_config . name = dev_name ( & spi - > dev ) ;
at25 - > nvmem_config . dev = & spi - > dev ;
2021-11-26 00:31:54 +03:00
at25 - > nvmem_config . read_only = at25 - > chip . flags & EE_READONLY ;
2016-02-26 22:59:22 +03:00
at25 - > nvmem_config . root_only = true ;
at25 - > nvmem_config . owner = THIS_MODULE ;
at25 - > nvmem_config . compat = true ;
at25 - > nvmem_config . base_dev = & spi - > dev ;
2016-04-24 22:28:07 +03:00
at25 - > nvmem_config . reg_read = at25_ee_read ;
at25 - > nvmem_config . reg_write = at25_ee_write ;
at25 - > nvmem_config . priv = at25 ;
2020-07-28 12:29:59 +03:00
at25 - > nvmem_config . stride = 1 ;
2016-04-24 22:28:07 +03:00
at25 - > nvmem_config . word_size = 1 ;
2021-11-26 00:31:54 +03:00
at25 - > nvmem_config . size = at25 - > chip . byte_len ;
2016-02-26 22:59:22 +03:00
2018-09-21 16:40:02 +03:00
at25 - > nvmem = devm_nvmem_register ( & spi - > dev , & at25 - > nvmem_config ) ;
2016-02-26 22:59:22 +03:00
if ( IS_ERR ( at25 - > nvmem ) )
return PTR_ERR ( at25 - > nvmem ) ;
2021-06-11 12:45:58 +03:00
dev_info ( & spi - > dev , " %d %s %s %s%s, pagesize %u \n " ,
2021-11-26 00:31:54 +03:00
( at25 - > chip . byte_len < 1024 ) ? at25 - > chip . byte_len : ( at25 - > chip . byte_len / 1024 ) ,
( at25 - > chip . byte_len < 1024 ) ? " Byte " : " KByte " ,
2021-06-11 12:45:58 +03:00
at25 - > chip . name , is_fram ? " fram " : " eeprom " ,
2021-11-26 00:31:54 +03:00
( at25 - > chip . flags & EE_READONLY ) ? " (readonly) " : " " ,
2021-06-11 12:45:58 +03:00
at25 - > chip . page_size ) ;
2007-02-12 11:52:48 +03:00
return 0 ;
}
/*-------------------------------------------------------------------------*/
static struct spi_driver at25_driver = {
. driver = {
. name = " at25 " ,
2013-10-14 19:14:59 +04:00
. of_match_table = at25_of_match ,
2021-06-11 12:45:58 +03:00
. dev_groups = sernum_groups ,
2007-02-12 11:52:48 +03:00
} ,
. probe = at25_probe ,
2021-09-23 20:24:53 +03:00
. id_table = at25_spi_ids ,
2007-02-12 11:52:48 +03:00
} ;
2012-01-22 11:38:22 +04:00
module_spi_driver ( at25_driver ) ;
2007-02-12 11:52:48 +03:00
MODULE_DESCRIPTION ( " Driver for most SPI EEPROMs " ) ;
MODULE_AUTHOR ( " David Brownell " ) ;
MODULE_LICENSE ( " GPL " ) ;
2009-09-23 03:46:08 +04:00
MODULE_ALIAS ( " spi:at25 " ) ;