2005-04-16 15:20:36 -07:00
/*
2008-01-27 18:14:45 +01:00
i2c - stub . c - I2C / SMBus chip emulator
2005-04-16 15:20:36 -07:00
Copyright ( c ) 2004 Mark M . Hoffman < mhoffman @ lightlink . com >
2014-01-29 20:40:08 +01:00
Copyright ( C ) 2007 , 2012 Jean Delvare < jdelvare @ suse . de >
2005-04-16 15:20:36 -07: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 .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# define DEBUG 1
# include <linux/init.h>
# include <linux/module.h>
# include <linux/kernel.h>
2007-10-13 23:56:31 +02:00
# include <linux/slab.h>
2005-04-16 15:20:36 -07:00
# include <linux/errno.h>
# include <linux/i2c.h>
2014-07-17 09:56:03 -07:00
# include <linux/list.h>
2005-04-16 15:20:36 -07:00
2007-10-13 23:56:31 +02:00
# define MAX_CHIPS 10
2014-07-17 09:56:03 -07: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-13 23:46:44 +02:00
2007-10-13 23:56:31 +02:00
static unsigned short chip_addr [ MAX_CHIPS ] ;
module_param_array ( chip_addr , ushort , NULL , S_IRUGO ) ;
MODULE_PARM_DESC ( chip_addr ,
2008-04-29 23:11:37 +02:00
" Chip addresses (up to 10, between 0x03 and 0x77) " ) ;
2007-10-13 23:56:31 +02:00
2014-07-17 09:56:03 -07:00
static unsigned long functionality = STUB_FUNC_DEFAULT ;
2009-12-06 17:06:29 +01:00
module_param ( functionality , ulong , S_IRUGO | S_IWUSR ) ;
MODULE_PARM_DESC ( functionality , " Override functionality bitfield " ) ;
2014-07-17 09:56:03 -07:00
struct smbus_block_data {
struct list_head node ;
u8 command ;
u8 len ;
u8 block [ I2C_SMBUS_BLOCK_MAX ] ;
} ;
2007-10-13 23:56:31 +02:00
struct stub_chip {
u8 pointer ;
2008-01-27 18:14:45 +01:00
u16 words [ 256 ] ; /* Byte operations use the LSB as per SMBus
specification */
2014-07-17 09:56:03 -07:00
struct list_head smbus_blocks ;
2007-10-13 23:56:31 +02:00
} ;
static struct stub_chip * stub_chips ;
2014-07-10 12:45:11 +02:00
static int stub_chips_nr ;
2005-04-16 15:20:36 -07:00
2014-07-17 09:56:03 -07: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 ;
}
2008-07-14 22:38:25 +02:00
/* Return negative errno on error. */
2012-10-28 21:37:00 +01: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-16 15:20:36 -07:00
{
s32 ret ;
2009-12-06 17:06:28 +01:00
int i , len ;
2007-10-13 23:56:31 +02:00
struct stub_chip * chip = NULL ;
2014-07-17 09:56:03 -07:00
struct smbus_block_data * b ;
2007-10-13 23:56:31 +02:00
/* Search for the right chip */
2014-07-10 12:45:11 +02:00
for ( i = 0 ; i < stub_chips_nr ; i + + ) {
2007-10-13 23:56:31 +02:00
if ( addr = = chip_addr [ i ] ) {
chip = stub_chips + i ;
break ;
}
}
if ( ! chip )
2006-08-13 23:46:44 +02:00
return - ENODEV ;
2005-04-16 15:20:36 -07: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-13 23:56:31 +02:00
chip - > pointer = command ;
2012-10-28 21:37:00 +01:00
dev_dbg ( & adap - > dev ,
" smbus byte - addr 0x%02x, wrote 0x%02x. \n " ,
addr , command ) ;
2005-04-16 15:20:36 -07:00
} else {
2008-01-27 18:14:45 +01:00
data - > byte = chip - > words [ chip - > pointer + + ] & 0xff ;
2012-10-28 21:37:00 +01:00
dev_dbg ( & adap - > dev ,
" smbus byte - addr 0x%02x, read 0x%02x. \n " ,
addr , data - > byte ) ;
2005-04-16 15:20:36 -07:00
}
ret = 0 ;
break ;
case I2C_SMBUS_BYTE_DATA :
if ( read_write = = I2C_SMBUS_WRITE ) {
2008-01-27 18:14:45 +01:00
chip - > words [ command ] & = 0xff00 ;
chip - > words [ command ] | = data - > byte ;
2012-10-28 21:37:00 +01:00
dev_dbg ( & adap - > dev ,
" smbus byte data - addr 0x%02x, wrote 0x%02x at 0x%02x. \n " ,
addr , data - > byte , command ) ;
2005-04-16 15:20:36 -07:00
} else {
2008-01-27 18:14:45 +01:00
data - > byte = chip - > words [ command ] & 0xff ;
2012-10-28 21:37:00 +01:00
dev_dbg ( & adap - > dev ,
" smbus byte data - addr 0x%02x, read 0x%02x at 0x%02x. \n " ,
addr , data - > byte , command ) ;
2005-04-16 15:20:36 -07:00
}
2007-10-13 23:56:31 +02:00
chip - > pointer = command + 1 ;
2005-04-16 15:20:36 -07:00
ret = 0 ;
break ;
case I2C_SMBUS_WORD_DATA :
if ( read_write = = I2C_SMBUS_WRITE ) {
2007-10-13 23:56:31 +02:00
chip - > words [ command ] = data - > word ;
2012-10-28 21:37:00 +01:00
dev_dbg ( & adap - > dev ,
" smbus word data - addr 0x%02x, wrote 0x%04x at 0x%02x. \n " ,
addr , data - > word , command ) ;
2005-04-16 15:20:36 -07:00
} else {
2007-10-13 23:56:31 +02:00
data - > word = chip - > words [ command ] ;
2012-10-28 21:37:00 +01:00
dev_dbg ( & adap - > dev ,
" smbus word data - addr 0x%02x, read 0x%04x at 0x%02x. \n " ,
addr , data - > word , command ) ;
2005-04-16 15:20:36 -07:00
}
ret = 0 ;
break ;
2009-12-06 17:06:28 +01:00
case I2C_SMBUS_I2C_BLOCK_DATA :
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-28 21:37:00 +01:00
dev_dbg ( & adap - > dev ,
" i2c block data - addr 0x%02x, wrote %d bytes at 0x%02x. \n " ,
addr , len , command ) ;
2009-12-06 17:06:28 +01:00
} else {
for ( i = 0 ; i < len ; i + + ) {
data - > block [ 1 + i ] =
chip - > words [ command + i ] & 0xff ;
}
2012-10-28 21:37:00 +01:00
dev_dbg ( & adap - > dev ,
" i2c block data - addr 0x%02x, read %d bytes at 0x%02x. \n " ,
addr , len , command ) ;
2009-12-06 17:06:28 +01:00
}
ret = 0 ;
break ;
2014-07-17 09:56:03 -07:00
case I2C_SMBUS_BLOCK_DATA :
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-16 15:20:36 -07:00
default :
dev_dbg ( & adap - > dev , " Unsupported I2C/SMBus command \n " ) ;
2008-07-14 22:38:25 +02:00
ret = - EOPNOTSUPP ;
2005-04-16 15:20:36 -07:00
break ;
} /* switch (size) */
return ret ;
}
static u32 stub_func ( struct i2c_adapter * adapter )
{
2014-07-17 09:56:03 -07:00
return STUB_FUNC_ALL & functionality ;
2005-04-16 15:20:36 -07:00
}
2006-09-03 22:39:46 +02:00
static const struct i2c_algorithm smbus_algorithm = {
2005-04-16 15:20:36 -07:00
. functionality = stub_func ,
. smbus_xfer = stub_xfer ,
} ;
static struct i2c_adapter stub_adapter = {
. owner = THIS_MODULE ,
2008-07-14 22:38:29 +02:00
. class = I2C_CLASS_HWMON | I2C_CLASS_SPD ,
2005-04-16 15:20:36 -07:00
. algo = & smbus_algorithm ,
. name = " SMBus stub driver " ,
} ;
static int __init i2c_stub_init ( void )
{
2007-10-13 23:56:31 +02:00
int i , ret ;
if ( ! chip_addr [ 0 ] ) {
2012-10-28 21:37:00 +01:00
pr_err ( " i2c-stub: Please specify a chip address \n " ) ;
2006-08-13 23:46:44 +02:00
return - ENODEV ;
}
2007-10-13 23:56:31 +02:00
for ( i = 0 ; i < MAX_CHIPS & & chip_addr [ i ] ; i + + ) {
if ( chip_addr [ i ] < 0x03 | | chip_addr [ i ] > 0x77 ) {
2012-10-28 21:37:00 +01:00
pr_err ( " i2c-stub: Invalid chip address 0x%02x \n " ,
chip_addr [ i ] ) ;
2007-10-13 23:56:31 +02:00
return - EINVAL ;
}
2012-10-28 21:37:00 +01:00
pr_info ( " i2c-stub: Virtual chip at 0x%02x \n " , chip_addr [ i ] ) ;
2006-08-13 23:46:44 +02:00
}
2007-10-13 23:56:31 +02:00
/* Allocate memory for all chips at once */
2014-07-10 12:45:11 +02:00
stub_chips_nr = i ;
stub_chips = kcalloc ( stub_chips_nr , sizeof ( struct stub_chip ) ,
GFP_KERNEL ) ;
2007-10-13 23:56:31 +02:00
if ( ! stub_chips ) {
2012-10-28 21:37:00 +01:00
pr_err ( " i2c-stub: Out of memory \n " ) ;
2007-10-13 23:56:31 +02:00
return - ENOMEM ;
}
2014-07-10 12:45:11 +02:00
for ( i = 0 ; i < stub_chips_nr ; i + + )
2014-07-17 09:56:03 -07:00
INIT_LIST_HEAD ( & stub_chips [ i ] . smbus_blocks ) ;
2007-10-13 23:56:31 +02:00
ret = i2c_add_adapter ( & stub_adapter ) ;
if ( ret )
kfree ( stub_chips ) ;
return ret ;
2005-04-16 15:20:36 -07:00
}
static void __exit i2c_stub_exit ( void )
{
i2c_del_adapter ( & stub_adapter ) ;
2007-10-13 23:56:31 +02:00
kfree ( stub_chips ) ;
2005-04-16 15:20:36 -07: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 ) ;