diff options
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/i2c-stub.c | 99 |
1 files changed, 94 insertions, 5 deletions
diff --git a/drivers/i2c/i2c-stub.c b/drivers/i2c/i2c-stub.c index 77e4849d2f2a..e0bb4655661d 100644 --- a/drivers/i2c/i2c-stub.c +++ b/drivers/i2c/i2c-stub.c @@ -27,29 +27,70 @@ #include <linux/slab.h> #include <linux/errno.h> #include <linux/i2c.h> +#include <linux/list.h> #define MAX_CHIPS 10 -#define STUB_FUNC (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | \ - I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | \ - I2C_FUNC_SMBUS_I2C_BLOCK) + +/* + * 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) static unsigned short chip_addr[MAX_CHIPS]; module_param_array(chip_addr, ushort, NULL, S_IRUGO); MODULE_PARM_DESC(chip_addr, "Chip addresses (up to 10, between 0x03 and 0x77)"); -static unsigned long functionality = STUB_FUNC; +static unsigned long functionality = STUB_FUNC_DEFAULT; module_param(functionality, ulong, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(functionality, "Override functionality bitfield"); +struct smbus_block_data { + struct list_head node; + u8 command; + u8 len; + u8 block[I2C_SMBUS_BLOCK_MAX]; +}; + struct stub_chip { u8 pointer; u16 words[256]; /* Byte operations use the LSB as per SMBus specification */ + struct list_head smbus_blocks; }; static struct stub_chip *stub_chips; +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; +} + /* Return negative errno on error. */ 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) @@ -57,6 +98,7 @@ static s32 stub_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, s32 ret; int i, len; struct stub_chip *chip = NULL; + struct smbus_block_data *b; /* Search for the right chip */ for (i = 0; i < MAX_CHIPS && chip_addr[i]; i++) { @@ -148,6 +190,51 @@ static s32 stub_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, ret = 0; break; + 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; + default: dev_dbg(&adap->dev, "Unsupported I2C/SMBus command\n"); ret = -EOPNOTSUPP; @@ -159,7 +246,7 @@ static s32 stub_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, static u32 stub_func(struct i2c_adapter *adapter) { - return STUB_FUNC & functionality; + return STUB_FUNC_ALL & functionality; } static const struct i2c_algorithm smbus_algorithm = { @@ -199,6 +286,8 @@ static int __init i2c_stub_init(void) pr_err("i2c-stub: Out of memory\n"); return -ENOMEM; } + for (i--; i >= 0; i--) + INIT_LIST_HEAD(&stub_chips[i].smbus_blocks); ret = i2c_add_adapter(&stub_adapter); if (ret) |