blob: ad52f070119898b8131b6fbeb4b2dcb08e8579a5 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Jean Delvare569be442008-01-27 18:14:45 +01002 i2c-stub.c - I2C/SMBus chip emulator
Linus Torvalds1da177e2005-04-16 15:20:36 -07003
4 Copyright (c) 2004 Mark M. Hoffman <mhoffman@lightlink.com>
Jean Delvare7c81c602014-01-29 20:40:08 +01005 Copyright (C) 2007, 2012 Jean Delvare <jdelvare@suse.de>
Linus Torvalds1da177e2005-04-16 15:20:36 -07006
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20*/
21
22#define DEBUG 1
23
Linus Torvalds1da177e2005-04-16 15:20:36 -070024#include <linux/init.h>
25#include <linux/module.h>
26#include <linux/kernel.h>
Jean Delvare9d90c1f2007-10-13 23:56:31 +020027#include <linux/slab.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070028#include <linux/errno.h>
29#include <linux/i2c.h>
Guenter Roeck6f16b752014-07-17 09:56:03 -070030#include <linux/list.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070031
Jean Delvare9d90c1f2007-10-13 23:56:31 +020032#define MAX_CHIPS 10
Guenter Roeck6f16b752014-07-17 09:56:03 -070033
34/*
35 * Support for I2C_FUNC_SMBUS_BLOCK_DATA is disabled by default and must
36 * be enabled explicitly by setting the I2C_FUNC_SMBUS_BLOCK_DATA bits
37 * in the 'functionality' module parameter.
38 */
39#define STUB_FUNC_DEFAULT \
40 (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | \
41 I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | \
42 I2C_FUNC_SMBUS_I2C_BLOCK)
43
44#define STUB_FUNC_ALL \
45 (STUB_FUNC_DEFAULT | I2C_FUNC_SMBUS_BLOCK_DATA)
Jean Delvare7a8d29c2006-08-13 23:46:44 +020046
Jean Delvare9d90c1f2007-10-13 23:56:31 +020047static unsigned short chip_addr[MAX_CHIPS];
48module_param_array(chip_addr, ushort, NULL, S_IRUGO);
49MODULE_PARM_DESC(chip_addr,
Jean Delvare85d69312008-04-29 23:11:37 +020050 "Chip addresses (up to 10, between 0x03 and 0x77)");
Jean Delvare9d90c1f2007-10-13 23:56:31 +020051
Guenter Roeck6f16b752014-07-17 09:56:03 -070052static unsigned long functionality = STUB_FUNC_DEFAULT;
Jean Delvare38f41f22009-12-06 17:06:29 +010053module_param(functionality, ulong, S_IRUGO | S_IWUSR);
54MODULE_PARM_DESC(functionality, "Override functionality bitfield");
55
Guenter Roeck6f16b752014-07-17 09:56:03 -070056struct smbus_block_data {
57 struct list_head node;
58 u8 command;
59 u8 len;
60 u8 block[I2C_SMBUS_BLOCK_MAX];
61};
62
Jean Delvare9d90c1f2007-10-13 23:56:31 +020063struct stub_chip {
64 u8 pointer;
Jean Delvare569be442008-01-27 18:14:45 +010065 u16 words[256]; /* Byte operations use the LSB as per SMBus
66 specification */
Guenter Roeck6f16b752014-07-17 09:56:03 -070067 struct list_head smbus_blocks;
Jean Delvare9d90c1f2007-10-13 23:56:31 +020068};
69
70static struct stub_chip *stub_chips;
Jean Delvare1dff5982014-07-10 12:45:11 +020071static int stub_chips_nr;
Linus Torvalds1da177e2005-04-16 15:20:36 -070072
Guenter Roeck6f16b752014-07-17 09:56:03 -070073static struct smbus_block_data *stub_find_block(struct device *dev,
74 struct stub_chip *chip,
75 u8 command, bool create)
76{
77 struct smbus_block_data *b, *rb = NULL;
78
79 list_for_each_entry(b, &chip->smbus_blocks, node) {
80 if (b->command == command) {
81 rb = b;
82 break;
83 }
84 }
85 if (rb == NULL && create) {
86 rb = devm_kzalloc(dev, sizeof(*rb), GFP_KERNEL);
87 if (rb == NULL)
88 return rb;
89 rb->command = command;
90 list_add(&rb->node, &chip->smbus_blocks);
91 }
92 return rb;
93}
94
David Brownell97140342008-07-14 22:38:25 +020095/* Return negative errno on error. */
Jean Delvare31d178b2012-10-28 21:37:00 +010096static s32 stub_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags,
97 char read_write, u8 command, int size, union i2c_smbus_data *data)
Linus Torvalds1da177e2005-04-16 15:20:36 -070098{
99 s32 ret;
Jean Delvare47103172009-12-06 17:06:28 +0100100 int i, len;
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200101 struct stub_chip *chip = NULL;
Guenter Roeck6f16b752014-07-17 09:56:03 -0700102 struct smbus_block_data *b;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700103
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200104 /* Search for the right chip */
Jean Delvare1dff5982014-07-10 12:45:11 +0200105 for (i = 0; i < stub_chips_nr; i++) {
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200106 if (addr == chip_addr[i]) {
107 chip = stub_chips + i;
108 break;
109 }
110 }
111 if (!chip)
Jean Delvare7a8d29c2006-08-13 23:46:44 +0200112 return -ENODEV;
113
Linus Torvalds1da177e2005-04-16 15:20:36 -0700114 switch (size) {
115
116 case I2C_SMBUS_QUICK:
117 dev_dbg(&adap->dev, "smbus quick - addr 0x%02x\n", addr);
118 ret = 0;
119 break;
120
121 case I2C_SMBUS_BYTE:
122 if (read_write == I2C_SMBUS_WRITE) {
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200123 chip->pointer = command;
Jean Delvare31d178b2012-10-28 21:37:00 +0100124 dev_dbg(&adap->dev,
125 "smbus byte - addr 0x%02x, wrote 0x%02x.\n",
126 addr, command);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127 } else {
Jean Delvare569be442008-01-27 18:14:45 +0100128 data->byte = chip->words[chip->pointer++] & 0xff;
Jean Delvare31d178b2012-10-28 21:37:00 +0100129 dev_dbg(&adap->dev,
130 "smbus byte - addr 0x%02x, read 0x%02x.\n",
131 addr, data->byte);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700132 }
133
134 ret = 0;
135 break;
136
137 case I2C_SMBUS_BYTE_DATA:
138 if (read_write == I2C_SMBUS_WRITE) {
Jean Delvare569be442008-01-27 18:14:45 +0100139 chip->words[command] &= 0xff00;
140 chip->words[command] |= data->byte;
Jean Delvare31d178b2012-10-28 21:37:00 +0100141 dev_dbg(&adap->dev,
142 "smbus byte data - addr 0x%02x, wrote 0x%02x at 0x%02x.\n",
143 addr, data->byte, command);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700144 } else {
Jean Delvare569be442008-01-27 18:14:45 +0100145 data->byte = chip->words[command] & 0xff;
Jean Delvare31d178b2012-10-28 21:37:00 +0100146 dev_dbg(&adap->dev,
147 "smbus byte data - addr 0x%02x, read 0x%02x at 0x%02x.\n",
148 addr, data->byte, command);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700149 }
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200150 chip->pointer = command + 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700151
152 ret = 0;
153 break;
154
155 case I2C_SMBUS_WORD_DATA:
156 if (read_write == I2C_SMBUS_WRITE) {
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200157 chip->words[command] = data->word;
Jean Delvare31d178b2012-10-28 21:37:00 +0100158 dev_dbg(&adap->dev,
159 "smbus word data - addr 0x%02x, wrote 0x%04x at 0x%02x.\n",
160 addr, data->word, command);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700161 } else {
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200162 data->word = chip->words[command];
Jean Delvare31d178b2012-10-28 21:37:00 +0100163 dev_dbg(&adap->dev,
164 "smbus word data - addr 0x%02x, read 0x%04x at 0x%02x.\n",
165 addr, data->word, command);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700166 }
167
168 ret = 0;
169 break;
170
Jean Delvare47103172009-12-06 17:06:28 +0100171 case I2C_SMBUS_I2C_BLOCK_DATA:
172 len = data->block[0];
173 if (read_write == I2C_SMBUS_WRITE) {
174 for (i = 0; i < len; i++) {
175 chip->words[command + i] &= 0xff00;
176 chip->words[command + i] |= data->block[1 + i];
177 }
Jean Delvare31d178b2012-10-28 21:37:00 +0100178 dev_dbg(&adap->dev,
179 "i2c block data - addr 0x%02x, wrote %d bytes at 0x%02x.\n",
180 addr, len, command);
Jean Delvare47103172009-12-06 17:06:28 +0100181 } else {
182 for (i = 0; i < len; i++) {
183 data->block[1 + i] =
184 chip->words[command + i] & 0xff;
185 }
Jean Delvare31d178b2012-10-28 21:37:00 +0100186 dev_dbg(&adap->dev,
187 "i2c block data - addr 0x%02x, read %d bytes at 0x%02x.\n",
188 addr, len, command);
Jean Delvare47103172009-12-06 17:06:28 +0100189 }
190
191 ret = 0;
192 break;
193
Guenter Roeck6f16b752014-07-17 09:56:03 -0700194 case I2C_SMBUS_BLOCK_DATA:
195 b = stub_find_block(&adap->dev, chip, command, false);
196 if (read_write == I2C_SMBUS_WRITE) {
197 len = data->block[0];
198 if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) {
199 ret = -EINVAL;
200 break;
201 }
202 if (b == NULL) {
203 b = stub_find_block(&adap->dev, chip, command,
204 true);
205 if (b == NULL) {
206 ret = -ENOMEM;
207 break;
208 }
209 }
210 /* Largest write sets read block length */
211 if (len > b->len)
212 b->len = len;
213 for (i = 0; i < len; i++)
214 b->block[i] = data->block[i + 1];
215 /* update for byte and word commands */
216 chip->words[command] = (b->block[0] << 8) | b->len;
217 dev_dbg(&adap->dev,
218 "smbus block data - addr 0x%02x, wrote %d bytes at 0x%02x.\n",
219 addr, len, command);
220 } else {
221 if (b == NULL) {
222 dev_dbg(&adap->dev,
223 "SMBus block read command without prior block write not supported\n");
224 ret = -EOPNOTSUPP;
225 break;
226 }
227 len = b->len;
228 data->block[0] = len;
229 for (i = 0; i < len; i++)
230 data->block[i + 1] = b->block[i];
231 dev_dbg(&adap->dev,
232 "smbus block data - addr 0x%02x, read %d bytes at 0x%02x.\n",
233 addr, len, command);
234 }
235
236 ret = 0;
237 break;
238
Linus Torvalds1da177e2005-04-16 15:20:36 -0700239 default:
240 dev_dbg(&adap->dev, "Unsupported I2C/SMBus command\n");
David Brownell97140342008-07-14 22:38:25 +0200241 ret = -EOPNOTSUPP;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700242 break;
243 } /* switch (size) */
244
245 return ret;
246}
247
248static u32 stub_func(struct i2c_adapter *adapter)
249{
Guenter Roeck6f16b752014-07-17 09:56:03 -0700250 return STUB_FUNC_ALL & functionality;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700251}
252
Jean Delvare8f9082c2006-09-03 22:39:46 +0200253static const struct i2c_algorithm smbus_algorithm = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700254 .functionality = stub_func,
255 .smbus_xfer = stub_xfer,
256};
257
258static struct i2c_adapter stub_adapter = {
259 .owner = THIS_MODULE,
Jean Delvare3401b2f2008-07-14 22:38:29 +0200260 .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700261 .algo = &smbus_algorithm,
262 .name = "SMBus stub driver",
263};
264
265static int __init i2c_stub_init(void)
266{
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200267 int i, ret;
268
269 if (!chip_addr[0]) {
Jean Delvare31d178b2012-10-28 21:37:00 +0100270 pr_err("i2c-stub: Please specify a chip address\n");
Jean Delvare7a8d29c2006-08-13 23:46:44 +0200271 return -ENODEV;
272 }
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200273
274 for (i = 0; i < MAX_CHIPS && chip_addr[i]; i++) {
275 if (chip_addr[i] < 0x03 || chip_addr[i] > 0x77) {
Jean Delvare31d178b2012-10-28 21:37:00 +0100276 pr_err("i2c-stub: Invalid chip address 0x%02x\n",
277 chip_addr[i]);
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200278 return -EINVAL;
279 }
280
Jean Delvare31d178b2012-10-28 21:37:00 +0100281 pr_info("i2c-stub: Virtual chip at 0x%02x\n", chip_addr[i]);
Jean Delvare7a8d29c2006-08-13 23:46:44 +0200282 }
283
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200284 /* Allocate memory for all chips at once */
Jean Delvare1dff5982014-07-10 12:45:11 +0200285 stub_chips_nr = i;
286 stub_chips = kcalloc(stub_chips_nr, sizeof(struct stub_chip),
287 GFP_KERNEL);
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200288 if (!stub_chips) {
Jean Delvare31d178b2012-10-28 21:37:00 +0100289 pr_err("i2c-stub: Out of memory\n");
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200290 return -ENOMEM;
291 }
Jean Delvare1dff5982014-07-10 12:45:11 +0200292 for (i = 0; i < stub_chips_nr; i++)
Guenter Roeck6f16b752014-07-17 09:56:03 -0700293 INIT_LIST_HEAD(&stub_chips[i].smbus_blocks);
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200294
295 ret = i2c_add_adapter(&stub_adapter);
296 if (ret)
297 kfree(stub_chips);
298 return ret;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700299}
300
301static void __exit i2c_stub_exit(void)
302{
303 i2c_del_adapter(&stub_adapter);
Jean Delvare9d90c1f2007-10-13 23:56:31 +0200304 kfree(stub_chips);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700305}
306
307MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>");
308MODULE_DESCRIPTION("I2C stub driver");
309MODULE_LICENSE("GPL");
310
311module_init(i2c_stub_init);
312module_exit(i2c_stub_exit);