diff options
author | Wolfram Sang <wsa+renesas@sang-engineering.com> | 2017-11-28 16:53:32 +0100 |
---|---|---|
committer | Wolfram Sang <wsa@the-dreams.de> | 2017-12-03 20:33:29 +0100 |
commit | 14911c6f48ec9571343ac36ae02f2db68bf9e7f9 (patch) | |
tree | 9791a0070072a85973191056652b315471db1774 /drivers | |
parent | i2c: designware: fix building driver as module (diff) | |
download | linux-14911c6f48ec9571343ac36ae02f2db68bf9e7f9.tar.xz linux-14911c6f48ec9571343ac36ae02f2db68bf9e7f9.zip |
i2c: gpio: add fault injector
Add fault injection capabilities to the i2c-gpio driver. When connected
to another I2C bus, it can create unusual states which the other I2C bus
master driver needs to handle. Only for debugging!
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/i2c/busses/Kconfig | 8 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-gpio.c | 111 |
2 files changed, 119 insertions, 0 deletions
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 009345d8f49d..a9805c7cb305 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -603,6 +603,14 @@ config I2C_GPIO This is a very simple bitbanging I2C driver utilizing the arch-neutral GPIO API to control the SCL and SDA lines. +config I2C_GPIO_FAULT_INJECTOR + bool "GPIO-based fault injector" + depends on I2C_GPIO + help + This adds some functionality to the i2c-gpio driver which can inject + faults to an I2C bus, so another bus master can be stress-tested. + This is for debugging. If unsure, say 'no'. + config I2C_HIGHLANDER tristate "Highlander FPGA SMBus interface" depends on SH_HIGHLANDER diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c index d80ea6ce91bb..3312ef981cb7 100644 --- a/drivers/i2c/busses/i2c-gpio.c +++ b/drivers/i2c/busses/i2c-gpio.c @@ -7,6 +7,8 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#include <linux/debugfs.h> +#include <linux/delay.h> #include <linux/i2c.h> #include <linux/i2c-algo-bit.h> #include <linux/i2c-gpio.h> @@ -23,6 +25,9 @@ struct i2c_gpio_private_data { struct i2c_adapter adap; struct i2c_algo_bit_data bit_data; struct i2c_gpio_platform_data pdata; +#ifdef CONFIG_I2C_GPIO_FAULT_INJECTOR + struct dentry *debug_dir; +#endif }; /* @@ -64,6 +69,108 @@ static int i2c_gpio_getscl(void *data) return gpiod_get_value(priv->scl); } +#ifdef CONFIG_I2C_GPIO_FAULT_INJECTOR +static struct dentry *i2c_gpio_debug_dir; + +#define setsda(bd, val) ((bd)->setsda((bd)->data, val)) +#define setscl(bd, val) ((bd)->setscl((bd)->data, val)) +#define getsda(bd) ((bd)->getsda((bd)->data)) +#define getscl(bd) ((bd)->getscl((bd)->data)) + +#define WIRE_ATTRIBUTE(wire) \ +static int fops_##wire##_get(void *data, u64 *val) \ +{ \ + struct i2c_gpio_private_data *priv = data; \ + \ + i2c_lock_adapter(&priv->adap); \ + *val = get##wire(&priv->bit_data); \ + i2c_unlock_adapter(&priv->adap); \ + return 0; \ +} \ +static int fops_##wire##_set(void *data, u64 val) \ +{ \ + struct i2c_gpio_private_data *priv = data; \ + \ + i2c_lock_adapter(&priv->adap); \ + set##wire(&priv->bit_data, val); \ + i2c_unlock_adapter(&priv->adap); \ + return 0; \ +} \ +DEFINE_DEBUGFS_ATTRIBUTE(fops_##wire, fops_##wire##_get, fops_##wire##_set, "%llu\n") + +WIRE_ATTRIBUTE(scl); +WIRE_ATTRIBUTE(sda); + +static int fops_incomplete_transfer_set(void *data, u64 addr) +{ + struct i2c_gpio_private_data *priv = data; + struct i2c_algo_bit_data *bit_data = &priv->bit_data; + int i, pattern; + + if (addr > 0x7f) + return -EINVAL; + + /* ADDR (7 bit) + RD (1 bit) + SDA hi (1 bit) */ + pattern = (addr << 2) | 3; + + i2c_lock_adapter(&priv->adap); + + /* START condition */ + setsda(bit_data, 0); + udelay(bit_data->udelay); + + /* Send ADDR+RD, request ACK, don't send STOP */ + for (i = 8; i >= 0; i--) { + setscl(bit_data, 0); + udelay(bit_data->udelay / 2); + setsda(bit_data, (pattern >> i) & 1); + udelay((bit_data->udelay + 1) / 2); + setscl(bit_data, 1); + udelay(bit_data->udelay); + } + + i2c_unlock_adapter(&priv->adap); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fops_incomplete_transfer, NULL, fops_incomplete_transfer_set, "%llu\n"); + +static void i2c_gpio_fault_injector_init(struct platform_device *pdev) +{ + struct i2c_gpio_private_data *priv = platform_get_drvdata(pdev); + + /* + * If there will be a debugfs-dir per i2c adapter somewhen, put the + * 'fault-injector' dir there. Until then, we have a global dir with + * all adapters as subdirs. + */ + if (!i2c_gpio_debug_dir) { + i2c_gpio_debug_dir = debugfs_create_dir("i2c-fault-injector", NULL); + if (!i2c_gpio_debug_dir) + return; + } + + priv->debug_dir = debugfs_create_dir(pdev->name, i2c_gpio_debug_dir); + if (!priv->debug_dir) + return; + + debugfs_create_file_unsafe("scl", 0600, priv->debug_dir, priv, &fops_scl); + debugfs_create_file_unsafe("sda", 0600, priv->debug_dir, priv, &fops_sda); + debugfs_create_file_unsafe("incomplete_transfer", 0200, priv->debug_dir, + priv, &fops_incomplete_transfer); +} + +static void i2c_gpio_fault_injector_exit(struct platform_device *pdev) +{ + struct i2c_gpio_private_data *priv = platform_get_drvdata(pdev); + + debugfs_remove_recursive(priv->debug_dir); +} +#else +static inline void i2c_gpio_fault_injector_init(struct platform_device *pdev) {} +static inline void i2c_gpio_fault_injector_exit(struct platform_device *pdev) {} +#endif /* CONFIG_I2C_GPIO_FAULT_INJECTOR*/ + static void of_i2c_gpio_get_props(struct device_node *np, struct i2c_gpio_platform_data *pdata) { @@ -228,6 +335,8 @@ static int i2c_gpio_probe(struct platform_device *pdev) pdata->scl_is_output_only ? ", no clock stretching" : ""); + i2c_gpio_fault_injector_init(pdev); + return 0; } @@ -236,6 +345,8 @@ static int i2c_gpio_remove(struct platform_device *pdev) struct i2c_gpio_private_data *priv; struct i2c_adapter *adap; + i2c_gpio_fault_injector_exit(pdev); + priv = platform_get_drvdata(pdev); adap = &priv->adap; |