2005-04-17 02:20:36 +04:00
/*
2008-01-27 20:14:45 +03:00
i2c - stub . c - I2C / SMBus chip emulator
2005-04-17 02:20:36 +04:00
Copyright ( c ) 2004 Mark M . Hoffman < mhoffman @ lightlink . com >
2014-07-10 14:56:59 +04:00
Copyright ( C ) 2007 - 2014 Jean Delvare < jdelvare @ suse . de >
2005-04-17 02:20:36 +04:00
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 .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
*/
# define DEBUG 1
# include <linux/errno.h>
# include <linux/i2c.h>
2016-02-21 01:33:39 +03:00
# include <linux/init.h>
# include <linux/kernel.h>
2014-07-17 20:56:03 +04:00
# include <linux/list.h>
2016-02-21 01:33:39 +03:00
# include <linux/module.h>
# include <linux/slab.h>
2005-04-17 02:20:36 +04:00
2007-10-14 01:56:31 +04:00
# define MAX_CHIPS 10
2014-07-17 20:56:03 +04:00
/*
* Support for I2C_FUNC_SMBUS_BLOCK_DATA is disabled by default and must
* be enabled explicitly by setting the I2C_FUNC_SMBUS_BLOCK_DATA bits
* in the ' functionality ' module parameter .
*/
# define STUB_FUNC_DEFAULT \
( I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | \
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | \
I2C_FUNC_SMBUS_I2C_BLOCK )
# define STUB_FUNC_ALL \
( STUB_FUNC_DEFAULT | I2C_FUNC_SMBUS_BLOCK_DATA )
2006-08-14 01:46:44 +04:00
2007-10-14 01:56:31 +04:00
static unsigned short chip_addr [ MAX_CHIPS ] ;
module_param_array ( chip_addr , ushort , NULL , S_IRUGO ) ;
MODULE_PARM_DESC ( chip_addr ,
2008-04-30 01:11:37 +04:00
" Chip addresses (up to 10, between 0x03 and 0x77) " ) ;
2007-10-14 01:56:31 +04:00
2014-07-17 20:56:03 +04:00
static unsigned long functionality = STUB_FUNC_DEFAULT ;
2009-12-06 19:06:29 +03:00
module_param ( functionality , ulong , S_IRUGO | S_IWUSR ) ;
MODULE_PARM_DESC ( functionality , " Override functionality bitfield " ) ;
2014-07-10 14:56:59 +04:00
/* Some chips have banked register ranges */
static u8 bank_reg [ MAX_CHIPS ] ;
module_param_array ( bank_reg , byte , NULL , S_IRUGO ) ;
MODULE_PARM_DESC ( bank_reg , " Bank register " ) ;
static u8 bank_mask [ MAX_CHIPS ] ;
module_param_array ( bank_mask , byte , NULL , S_IRUGO ) ;
MODULE_PARM_DESC ( bank_mask , " Bank value mask " ) ;
static u8 bank_start [ MAX_CHIPS ] ;
module_param_array ( bank_start , byte , NULL , S_IRUGO ) ;
MODULE_PARM_DESC ( bank_start , " First banked register " ) ;
static u8 bank_end [ MAX_CHIPS ] ;
module_param_array ( bank_end , byte , NULL , S_IRUGO ) ;
MODULE_PARM_DESC ( bank_end , " Last banked register " ) ;
2014-07-17 20:56:03 +04:00
struct smbus_block_data {
struct list_head node ;
u8 command ;
u8 len ;
u8 block [ I2C_SMBUS_BLOCK_MAX ] ;
} ;
2007-10-14 01:56:31 +04:00
struct stub_chip {
u8 pointer ;
2008-01-27 20:14:45 +03:00
u16 words [ 256 ] ; /* Byte operations use the LSB as per SMBus
specification */
2014-07-17 20:56:03 +04:00
struct list_head smbus_blocks ;
2014-07-10 14:56:59 +04:00
/* For chips with banks, extra registers are allocated dynamically */
u8 bank_reg ;
u8 bank_shift ;
u8 bank_mask ;
u8 bank_sel ; /* Currently selected bank */
u8 bank_start ;
u8 bank_end ;
u16 bank_size ;
u16 * bank_words ; /* Room for bank_mask * bank_size registers */
2007-10-14 01:56:31 +04:00
} ;
static struct stub_chip * stub_chips ;
2014-07-10 14:45:11 +04:00
static int stub_chips_nr ;
2005-04-17 02:20:36 +04:00
2014-07-17 20:56:03 +04:00
static struct smbus_block_data * stub_find_block ( struct device * dev ,
struct stub_chip * chip ,
u8 command , bool create )
{
struct smbus_block_data * b , * rb = NULL ;
list_for_each_entry ( b , & chip - > smbus_blocks , node ) {
if ( b - > command = = command ) {
rb = b ;
break ;
}
}
if ( rb = = NULL & & create ) {
rb = devm_kzalloc ( dev , sizeof ( * rb ) , GFP_KERNEL ) ;
if ( rb = = NULL )
return rb ;
rb - > command = command ;
list_add ( & rb - > node , & chip - > smbus_blocks ) ;
}
return rb ;
}
2014-07-10 14:56:59 +04:00
static u16 * stub_get_wordp ( struct stub_chip * chip , u8 offset )
{
if ( chip - > bank_sel & &
offset > = chip - > bank_start & & offset < = chip - > bank_end )
return chip - > bank_words +
( chip - > bank_sel - 1 ) * chip - > bank_size +
offset - chip - > bank_start ;
else
return chip - > words + offset ;
}
2008-07-15 00:38:25 +04:00
/* Return negative errno on error. */
2012-10-29 00:37:00 +04:00
static s32 stub_xfer ( struct i2c_adapter * adap , u16 addr , unsigned short flags ,
char read_write , u8 command , int size , union i2c_smbus_data * data )
2005-04-17 02:20:36 +04:00
{
s32 ret ;
2009-12-06 19:06:28 +03:00
int i , len ;
2007-10-14 01:56:31 +04:00
struct stub_chip * chip = NULL ;
2014-07-17 20:56:03 +04:00
struct smbus_block_data * b ;
2014-07-10 14:56:59 +04:00
u16 * wordp ;
2007-10-14 01:56:31 +04:00
/* Search for the right chip */
2014-07-10 14:45:11 +04:00
for ( i = 0 ; i < stub_chips_nr ; i + + ) {
2007-10-14 01:56:31 +04:00
if ( addr = = chip_addr [ i ] ) {
chip = stub_chips + i ;
break ;
}
}
if ( ! chip )
2006-08-14 01:46:44 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
switch ( size ) {
case I2C_SMBUS_QUICK :
dev_dbg ( & adap - > dev , " smbus quick - addr 0x%02x \n " , addr ) ;
ret = 0 ;
break ;
case I2C_SMBUS_BYTE :
if ( read_write = = I2C_SMBUS_WRITE ) {
2007-10-14 01:56:31 +04:00
chip - > pointer = command ;
2012-10-29 00:37:00 +04:00
dev_dbg ( & adap - > dev ,
" smbus byte - addr 0x%02x, wrote 0x%02x. \n " ,
addr , command ) ;
2005-04-17 02:20:36 +04:00
} else {
2014-07-10 14:56:59 +04:00
wordp = stub_get_wordp ( chip , chip - > pointer + + ) ;
data - > byte = * wordp & 0xff ;
2012-10-29 00:37:00 +04:00
dev_dbg ( & adap - > dev ,
" smbus byte - addr 0x%02x, read 0x%02x. \n " ,
addr , data - > byte ) ;
2005-04-17 02:20:36 +04:00
}
ret = 0 ;
break ;
case I2C_SMBUS_BYTE_DATA :
2014-07-10 14:56:59 +04:00
wordp = stub_get_wordp ( chip , command ) ;
2005-04-17 02:20:36 +04:00
if ( read_write = = I2C_SMBUS_WRITE ) {
2014-07-10 14:56:59 +04:00
* wordp & = 0xff00 ;
* wordp | = data - > byte ;
2012-10-29 00:37:00 +04:00
dev_dbg ( & adap - > dev ,
" smbus byte data - addr 0x%02x, wrote 0x%02x at 0x%02x. \n " ,
addr , data - > byte , command ) ;
2014-07-10 14:56:59 +04:00
/* Set the bank as needed */
if ( chip - > bank_words & & command = = chip - > bank_reg ) {
chip - > bank_sel =
( data - > byte > > chip - > bank_shift )
& chip - > bank_mask ;
dev_dbg ( & adap - > dev ,
" switching to bank %u. \n " ,
chip - > bank_sel ) ;
}
2005-04-17 02:20:36 +04:00
} else {
2014-07-10 14:56:59 +04:00
data - > byte = * wordp & 0xff ;
2012-10-29 00:37:00 +04:00
dev_dbg ( & adap - > dev ,
" smbus byte data - addr 0x%02x, read 0x%02x at 0x%02x. \n " ,
addr , data - > byte , command ) ;
2005-04-17 02:20:36 +04:00
}
2007-10-14 01:56:31 +04:00
chip - > pointer = command + 1 ;
2005-04-17 02:20:36 +04:00
ret = 0 ;
break ;
case I2C_SMBUS_WORD_DATA :
2014-07-10 14:56:59 +04:00
wordp = stub_get_wordp ( chip , command ) ;
2005-04-17 02:20:36 +04:00
if ( read_write = = I2C_SMBUS_WRITE ) {
2014-07-10 14:56:59 +04:00
* wordp = data - > word ;
2012-10-29 00:37:00 +04:00
dev_dbg ( & adap - > dev ,
" smbus word data - addr 0x%02x, wrote 0x%04x at 0x%02x. \n " ,
addr , data - > word , command ) ;
2005-04-17 02:20:36 +04:00
} else {
2014-07-10 14:56:59 +04:00
data - > word = * wordp ;
2012-10-29 00:37:00 +04:00
dev_dbg ( & adap - > dev ,
" smbus word data - addr 0x%02x, read 0x%04x at 0x%02x. \n " ,
addr , data - > word , command ) ;
2005-04-17 02:20:36 +04:00
}
ret = 0 ;
break ;
2009-12-06 19:06:28 +03:00
case I2C_SMBUS_I2C_BLOCK_DATA :
2014-07-10 14:56:59 +04:00
/*
* We ignore banks here , because banked chips don ' t use I2C
* block transfers
*/
2014-07-13 19:17:17 +04:00
if ( data - > block [ 0 ] > 256 - command ) /* Avoid overrun */
data - > block [ 0 ] = 256 - command ;
2009-12-06 19:06:28 +03:00
len = data - > block [ 0 ] ;
if ( read_write = = I2C_SMBUS_WRITE ) {
for ( i = 0 ; i < len ; i + + ) {
chip - > words [ command + i ] & = 0xff00 ;
chip - > words [ command + i ] | = data - > block [ 1 + i ] ;
}
2012-10-29 00:37:00 +04:00
dev_dbg ( & adap - > dev ,
" i2c block data - addr 0x%02x, wrote %d bytes at 0x%02x. \n " ,
addr , len , command ) ;
2009-12-06 19:06:28 +03:00
} else {
for ( i = 0 ; i < len ; i + + ) {
data - > block [ 1 + i ] =
chip - > words [ command + i ] & 0xff ;
}
2012-10-29 00:37:00 +04:00
dev_dbg ( & adap - > dev ,
" i2c block data - addr 0x%02x, read %d bytes at 0x%02x. \n " ,
addr , len , command ) ;
2009-12-06 19:06:28 +03:00
}
ret = 0 ;
break ;
2014-07-17 20:56:03 +04:00
case I2C_SMBUS_BLOCK_DATA :
2014-07-10 14:56:59 +04:00
/*
* We ignore banks here , because chips typically don ' t use both
* banks and SMBus block transfers
*/
2014-07-17 20:56:03 +04:00
b = stub_find_block ( & adap - > dev , chip , command , false ) ;
if ( read_write = = I2C_SMBUS_WRITE ) {
len = data - > block [ 0 ] ;
if ( len = = 0 | | len > I2C_SMBUS_BLOCK_MAX ) {
ret = - EINVAL ;
break ;
}
if ( b = = NULL ) {
b = stub_find_block ( & adap - > dev , chip , command ,
true ) ;
if ( b = = NULL ) {
ret = - ENOMEM ;
break ;
}
}
/* Largest write sets read block length */
if ( len > b - > len )
b - > len = len ;
for ( i = 0 ; i < len ; i + + )
b - > block [ i ] = data - > block [ i + 1 ] ;
/* update for byte and word commands */
chip - > words [ command ] = ( b - > block [ 0 ] < < 8 ) | b - > len ;
dev_dbg ( & adap - > dev ,
" smbus block data - addr 0x%02x, wrote %d bytes at 0x%02x. \n " ,
addr , len , command ) ;
} else {
if ( b = = NULL ) {
dev_dbg ( & adap - > dev ,
" SMBus block read command without prior block write not supported \n " ) ;
ret = - EOPNOTSUPP ;
break ;
}
len = b - > len ;
data - > block [ 0 ] = len ;
for ( i = 0 ; i < len ; i + + )
data - > block [ i + 1 ] = b - > block [ i ] ;
dev_dbg ( & adap - > dev ,
" smbus block data - addr 0x%02x, read %d bytes at 0x%02x. \n " ,
addr , len , command ) ;
}
ret = 0 ;
break ;
2005-04-17 02:20:36 +04:00
default :
dev_dbg ( & adap - > dev , " Unsupported I2C/SMBus command \n " ) ;
2008-07-15 00:38:25 +04:00
ret = - EOPNOTSUPP ;
2005-04-17 02:20:36 +04:00
break ;
} /* switch (size) */
return ret ;
}
static u32 stub_func ( struct i2c_adapter * adapter )
{
2014-07-17 20:56:03 +04:00
return STUB_FUNC_ALL & functionality ;
2005-04-17 02:20:36 +04:00
}
2006-09-04 00:39:46 +04:00
static const struct i2c_algorithm smbus_algorithm = {
2005-04-17 02:20:36 +04:00
. functionality = stub_func ,
. smbus_xfer = stub_xfer ,
} ;
static struct i2c_adapter stub_adapter = {
. owner = THIS_MODULE ,
2008-07-15 00:38:29 +04:00
. class = I2C_CLASS_HWMON | I2C_CLASS_SPD ,
2005-04-17 02:20:36 +04:00
. algo = & smbus_algorithm ,
. name = " SMBus stub driver " ,
} ;
2014-07-10 14:56:59 +04:00
static int __init i2c_stub_allocate_banks ( int i )
{
struct stub_chip * chip = stub_chips + i ;
chip - > bank_reg = bank_reg [ i ] ;
chip - > bank_start = bank_start [ i ] ;
chip - > bank_end = bank_end [ i ] ;
chip - > bank_size = bank_end [ i ] - bank_start [ i ] + 1 ;
/* We assume that all bits in the mask are contiguous */
chip - > bank_mask = bank_mask [ i ] ;
while ( ! ( chip - > bank_mask & 1 ) ) {
chip - > bank_shift + + ;
chip - > bank_mask > > = 1 ;
}
chip - > bank_words = kzalloc ( chip - > bank_mask * chip - > bank_size *
sizeof ( u16 ) , GFP_KERNEL ) ;
if ( ! chip - > bank_words )
return - ENOMEM ;
pr_debug ( " i2c-stub: Allocated %u banks of %u words each (registers 0x%02x to 0x%02x) \n " ,
chip - > bank_mask , chip - > bank_size , chip - > bank_start ,
chip - > bank_end ) ;
return 0 ;
}
static void i2c_stub_free ( void )
{
int i ;
for ( i = 0 ; i < stub_chips_nr ; i + + )
kfree ( stub_chips [ i ] . bank_words ) ;
kfree ( stub_chips ) ;
}
2005-04-17 02:20:36 +04:00
static int __init i2c_stub_init ( void )
{
2007-10-14 01:56:31 +04:00
int i , ret ;
if ( ! chip_addr [ 0 ] ) {
2012-10-29 00:37:00 +04:00
pr_err ( " i2c-stub: Please specify a chip address \n " ) ;
2006-08-14 01:46:44 +04:00
return - ENODEV ;
}
2007-10-14 01:56:31 +04:00
for ( i = 0 ; i < MAX_CHIPS & & chip_addr [ i ] ; i + + ) {
if ( chip_addr [ i ] < 0x03 | | chip_addr [ i ] > 0x77 ) {
2012-10-29 00:37:00 +04:00
pr_err ( " i2c-stub: Invalid chip address 0x%02x \n " ,
chip_addr [ i ] ) ;
2007-10-14 01:56:31 +04:00
return - EINVAL ;
}
2012-10-29 00:37:00 +04:00
pr_info ( " i2c-stub: Virtual chip at 0x%02x \n " , chip_addr [ i ] ) ;
2006-08-14 01:46:44 +04:00
}
2007-10-14 01:56:31 +04:00
/* Allocate memory for all chips at once */
2014-07-10 14:45:11 +04:00
stub_chips_nr = i ;
stub_chips = kcalloc ( stub_chips_nr , sizeof ( struct stub_chip ) ,
GFP_KERNEL ) ;
2007-10-14 01:56:31 +04:00
if ( ! stub_chips ) {
2012-10-29 00:37:00 +04:00
pr_err ( " i2c-stub: Out of memory \n " ) ;
2007-10-14 01:56:31 +04:00
return - ENOMEM ;
}
2014-07-10 14:56:59 +04:00
for ( i = 0 ; i < stub_chips_nr ; i + + ) {
2014-07-17 20:56:03 +04:00
INIT_LIST_HEAD ( & stub_chips [ i ] . smbus_blocks ) ;
2007-10-14 01:56:31 +04:00
2014-07-10 14:56:59 +04:00
/* Allocate extra memory for banked register ranges */
if ( bank_mask [ i ] ) {
ret = i2c_stub_allocate_banks ( i ) ;
if ( ret )
goto fail_free ;
}
}
2007-10-14 01:56:31 +04:00
ret = i2c_add_adapter ( & stub_adapter ) ;
if ( ret )
2014-07-10 14:56:59 +04:00
goto fail_free ;
return 0 ;
fail_free :
i2c_stub_free ( ) ;
2007-10-14 01:56:31 +04:00
return ret ;
2005-04-17 02:20:36 +04:00
}
static void __exit i2c_stub_exit ( void )
{
i2c_del_adapter ( & stub_adapter ) ;
2014-07-10 14:56:59 +04:00
i2c_stub_free ( ) ;
2005-04-17 02:20:36 +04:00
}
MODULE_AUTHOR ( " Mark M. Hoffman <mhoffman@lightlink.com> " ) ;
MODULE_DESCRIPTION ( " I2C stub driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( i2c_stub_init ) ;
module_exit ( i2c_stub_exit ) ;