diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-04 10:57:43 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-04 10:57:43 -0800 |
commit | 4141cf676b9e345d3ddeb1710dd3156a09c50244 (patch) | |
tree | 4b2b6249e99efdd47eafa5780f106d593f64708b /drivers/i2c | |
parent | 3462ac57033e79a87dbae2497773f22b9c536fbc (diff) | |
parent | e38c85644e11c6dc5a39305c96b617f63403423d (diff) |
Merge branch 'i2c/for-4.16' of git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux
Pull i2c updates from Wolfram Sang:
"I2C has the following changes for you:
- new flag to mark DMA safe buffers in i2c_msg. Also, some
infrastructure around it. And docs.
- huge refactoring of the at24 driver led by the new maintainer
Bartosz
- update I2C bus recovery to send STOP after recovery
- conversion from gpio to gpiod for I2C bus recovery
- adding a fault-injector to the i2c-gpio driver
- lots of small driver improvements, and bigger ones to
i2c-sh_mobile"
* 'i2c/for-4.16' of git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux: (99 commits)
i2c: mv64xxx: Add myself as maintainer for this driver
i2c: mv64xxx: Fix clock resource by adding an optional bus clock
i2c: mv64xxx: Remove useless test before clk_disable_unprepare
i2c: mxs: use true and false for boolean values
i2c: meson: update doc description to fix build warnings
i2c: meson: add configurable divider factors
dt-bindings: i2c: update documentation for the Meson-AXG
i2c: imx-lpi2c: add runtime pm support
i2c: rcar: fix some trivial typos in comments
i2c: davinci: fix the cpufreq transition
i2c: rk3x: add proper kerneldoc header
i2c: rk3x: account for const type of of_device_id.data
i2c: acorn: remove outdated path from file header
i2c: acorn: add MODULE_LICENSE tag
i2c: rcar: implement bus recovery
i2c: send STOP after successful bus recovery
i2c: ensure SDA is released in recovery if SDA is controllable
i2c: add 'set_sda' to bus_recovery_info
i2c: add identifier in declarations for i2c_bus_recovery
i2c: make kerneldoc about bus recovery more precise
...
Diffstat (limited to 'drivers/i2c')
28 files changed, 761 insertions, 454 deletions
diff --git a/drivers/i2c/algos/i2c-algo-bit.c b/drivers/i2c/algos/i2c-algo-bit.c index 1147bddb8b2c..3df0efd69ae3 100644 --- a/drivers/i2c/algos/i2c-algo-bit.c +++ b/drivers/i2c/algos/i2c-algo-bit.c @@ -649,6 +649,11 @@ static int __i2c_bit_add_bus(struct i2c_adapter *adap, if (bit_adap->getscl == NULL) adap->quirks = &i2c_bit_quirk_no_clk_stretch; + /* Bring bus to a known state. Looks like STOP if bus is not free yet */ + setscl(bit_adap, 1); + udelay(bit_adap->udelay); + setsda(bit_adap, 1); + ret = add_adapter(adap); if (ret < 0) return ret; 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-acorn.c b/drivers/i2c/busses/i2c-acorn.c index 9d7be5af2bf2..f4a5ae69bf6a 100644 --- a/drivers/i2c/busses/i2c-acorn.c +++ b/drivers/i2c/busses/i2c-acorn.c @@ -1,5 +1,5 @@ /* - * linux/drivers/acorn/char/i2c.c + * ARM IOC/IOMD i2c driver. * * Copyright (C) 2000 Russell King * @@ -7,8 +7,6 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * - * ARM IOC/IOMD i2c driver. - * * On Acorn machines, the following i2c devices are on the bus: * - PCF8583 real time clock & static RAM */ @@ -94,3 +92,7 @@ static int __init i2c_ioc_init(void) } module_init(i2c_ioc_init); + +MODULE_AUTHOR("Russell King <linux@armlinux.org.uk>"); +MODULE_DESCRIPTION("ARM IOC/IOMD i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/busses/i2c-davinci.c b/drivers/i2c/busses/i2c-davinci.c index 2ead9b9eebb7..75d6ab177055 100644 --- a/drivers/i2c/busses/i2c-davinci.c +++ b/drivers/i2c/busses/i2c-davinci.c @@ -33,7 +33,7 @@ #include <linux/io.h> #include <linux/slab.h> #include <linux/cpufreq.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/of_device.h> #include <linux/platform_data/i2c-davinci.h> #include <linux/pm_runtime.h> @@ -139,7 +139,6 @@ struct davinci_i2c_dev { u8 terminate; struct i2c_adapter adapter; #ifdef CONFIG_CPU_FREQ - struct completion xfr_complete; struct notifier_block freq_transition; #endif struct davinci_i2c_platform_data *pdata; @@ -294,7 +293,7 @@ static int i2c_davinci_init(struct davinci_i2c_dev *dev) } /* - * This routine does i2c bus recovery by using i2c_generic_gpio_recovery + * This routine does i2c bus recovery by using i2c_generic_scl_recovery * which is provided by I2C Bus recovery infrastructure. */ static void davinci_i2c_prepare_recovery(struct i2c_adapter *adap) @@ -316,7 +315,7 @@ static void davinci_i2c_unprepare_recovery(struct i2c_adapter *adap) } static struct i2c_bus_recovery_info davinci_i2c_gpio_recovery_info = { - .recover_bus = i2c_generic_gpio_recovery, + .recover_bus = i2c_generic_scl_recovery, .prepare_recovery = davinci_i2c_prepare_recovery, .unprepare_recovery = davinci_i2c_unprepare_recovery, }; @@ -567,9 +566,6 @@ i2c_davinci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) } ret = num; -#ifdef CONFIG_CPU_FREQ - complete(&dev->xfr_complete); -#endif out: pm_runtime_mark_last_busy(dev->dev); @@ -717,13 +713,15 @@ static int i2c_davinci_cpufreq_transition(struct notifier_block *nb, struct davinci_i2c_dev *dev; dev = container_of(nb, struct davinci_i2c_dev, freq_transition); + + i2c_lock_adapter(&dev->adapter); if (val == CPUFREQ_PRECHANGE) { - wait_for_completion(&dev->xfr_complete); davinci_i2c_reset_ctrl(dev, 0); } else if (val == CPUFREQ_POSTCHANGE) { i2c_davinci_calc_clk_dividers(dev); davinci_i2c_reset_ctrl(dev, 1); } + i2c_unlock_adapter(&dev->adapter); return 0; } @@ -769,6 +767,7 @@ static int davinci_i2c_probe(struct platform_device *pdev) struct davinci_i2c_dev *dev; struct i2c_adapter *adap; struct resource *mem; + struct i2c_bus_recovery_info *rinfo; int r, irq; irq = platform_get_irq(pdev, 0); @@ -789,9 +788,7 @@ static int davinci_i2c_probe(struct platform_device *pdev) } init_completion(&dev->cmd_complete); -#ifdef CONFIG_CPU_FREQ - init_completion(&dev->xfr_complete); -#endif + dev->dev = &pdev->dev; dev->irq = irq; dev->pdata = dev_get_platdata(&pdev->dev); @@ -868,10 +865,20 @@ static int davinci_i2c_probe(struct platform_device *pdev) if (dev->pdata->has_pfunc) adap->bus_recovery_info = &davinci_i2c_scl_recovery_info; - else if (dev->pdata->scl_pin) { - adap->bus_recovery_info = &davinci_i2c_gpio_recovery_info; - adap->bus_recovery_info->scl_gpio = dev->pdata->scl_pin; - adap->bus_recovery_info->sda_gpio = dev->pdata->sda_pin; + else if (dev->pdata->gpio_recovery) { + rinfo = &davinci_i2c_gpio_recovery_info; + adap->bus_recovery_info = rinfo; + rinfo->scl_gpiod = devm_gpiod_get(&pdev->dev, "scl", + GPIOD_OUT_HIGH_OPEN_DRAIN); + if (IS_ERR(rinfo->scl_gpiod)) { + r = PTR_ERR(rinfo->scl_gpiod); + goto err_unuse_clocks; + } + rinfo->sda_gpiod = devm_gpiod_get(&pdev->dev, "sda", GPIOD_IN); + if (IS_ERR(rinfo->sda_gpiod)) { + r = PTR_ERR(rinfo->sda_gpiod); + goto err_unuse_clocks; + } } adap->nr = pdev->id; diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index d1a69372432f..27ebd90de43b 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -21,6 +21,7 @@ * ---------------------------------------------------------------------------- * */ +#include <linux/clk.h> #include <linux/delay.h> #include <linux/export.h> #include <linux/errno.h> @@ -185,6 +186,19 @@ unsigned long i2c_dw_clk_rate(struct dw_i2c_dev *dev) return dev->get_clk_rate_khz(dev); } +int i2c_dw_prepare_clk(struct dw_i2c_dev *dev, bool prepare) +{ + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->clk); + + if (prepare) + return clk_prepare_enable(dev->clk); + + clk_disable_unprepare(dev->clk); + return 0; +} +EXPORT_SYMBOL_GPL(i2c_dw_prepare_clk); + int i2c_dw_acquire_lock(struct dw_i2c_dev *dev) { int ret; @@ -217,7 +231,11 @@ int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev) while (dw_readl(dev, DW_IC_STATUS) & DW_IC_STATUS_ACTIVITY) { if (timeout <= 0) { dev_warn(dev->dev, "timeout waiting for bus ready\n"); - return -ETIMEDOUT; + i2c_recover_bus(&dev->adapter); + + if (dw_readl(dev, DW_IC_STATUS) & DW_IC_STATUS_ACTIVITY) + return -ETIMEDOUT; + return 0; } timeout--; usleep_range(1000, 1100); diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 9fee4c054d3d..8707c76b2fee 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -284,6 +284,7 @@ struct dw_i2c_dev { void (*disable_int)(struct dw_i2c_dev *dev); int (*init)(struct dw_i2c_dev *dev); int mode; + struct i2c_bus_recovery_info rinfo; }; #define ACCESS_SWAP 0x00000001 @@ -299,6 +300,7 @@ u32 i2c_dw_scl_lcnt(u32 ic_clk, u32 tLOW, u32 tf, int offset); void __i2c_dw_enable(struct dw_i2c_dev *dev, bool enable); void __i2c_dw_enable_and_wait(struct dw_i2c_dev *dev, bool enable); unsigned long i2c_dw_clk_rate(struct dw_i2c_dev *dev); +int i2c_dw_prepare_clk(struct dw_i2c_dev *dev, bool prepare); int i2c_dw_acquire_lock(struct dw_i2c_dev *dev); void i2c_dw_release_lock(struct dw_i2c_dev *dev); int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev); diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 418c233075d3..ae691884d071 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -25,11 +25,13 @@ #include <linux/err.h> #include <linux/errno.h> #include <linux/export.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/reset.h> #include "i2c-designware-core.h" @@ -443,6 +445,7 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) if (!wait_for_completion_timeout(&dev->cmd_complete, adap->timeout)) { dev_err(dev->dev, "controller timed out\n"); /* i2c_dw_init implicitly disables the adapter */ + i2c_recover_bus(&dev->adapter); i2c_dw_init_master(dev); ret = -ETIMEDOUT; goto done; @@ -613,6 +616,56 @@ static irqreturn_t i2c_dw_isr(int this_irq, void *dev_id) return IRQ_HANDLED; } +static void i2c_dw_prepare_recovery(struct i2c_adapter *adap) +{ + struct dw_i2c_dev *dev = i2c_get_adapdata(adap); + + i2c_dw_disable(dev); + reset_control_assert(dev->rst); + i2c_dw_prepare_clk(dev, false); +} + +static void i2c_dw_unprepare_recovery(struct i2c_adapter *adap) +{ + struct dw_i2c_dev *dev = i2c_get_adapdata(adap); + + i2c_dw_prepare_clk(dev, true); + reset_control_deassert(dev->rst); + i2c_dw_init_master(dev); +} + +static int i2c_dw_init_recovery_info(struct dw_i2c_dev *dev) +{ + struct i2c_bus_recovery_info *rinfo = &dev->rinfo; + struct i2c_adapter *adap = &dev->adapter; + struct gpio_desc *gpio; + int r; + + gpio = devm_gpiod_get(dev->dev, "scl", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) { + r = PTR_ERR(gpio); + if (r == -ENOENT) + return 0; + return r; + } + rinfo->scl_gpiod = gpio; + + gpio = devm_gpiod_get_optional(dev->dev, "sda", GPIOD_IN); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + rinfo->sda_gpiod = gpio; + + rinfo->recover_bus = i2c_generic_scl_recovery; + rinfo->prepare_recovery = i2c_dw_prepare_recovery; + rinfo->unprepare_recovery = i2c_dw_unprepare_recovery; + adap->bus_recovery_info = rinfo; + + dev_info(dev->dev, "running with gpio recovery mode! scl%s", + rinfo->sda_gpiod ? ",sda" : ""); + + return 0; +} + int i2c_dw_probe(struct dw_i2c_dev *dev) { struct i2c_adapter *adap = &dev->adapter; @@ -652,6 +705,10 @@ int i2c_dw_probe(struct dw_i2c_dev *dev) return ret; } + ret = i2c_dw_init_recovery_info(dev); + if (ret) + return ret; + /* * Increment PM usage count during adapter registration in order to * avoid possible spurious runtime suspend when adapter device is diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 153b947702c5..5660daf6c92e 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -202,29 +202,6 @@ static void i2c_dw_configure_slave(struct dw_i2c_dev *dev) DW_IC_CON_RESTART_EN | DW_IC_CON_STOP_DET_IFADDRESSED; dev->mode = DW_IC_SLAVE; - - switch (dev->clk_freq) { - case 100000: - dev->slave_cfg |= DW_IC_CON_SPEED_STD; - break; - case 3400000: - dev->slave_cfg |= DW_IC_CON_SPEED_HIGH; - break; - default: - dev->slave_cfg |= DW_IC_CON_SPEED_FAST; - } -} - -static int i2c_dw_plat_prepare_clk(struct dw_i2c_dev *i_dev, bool prepare) -{ - if (IS_ERR(i_dev->clk)) - return PTR_ERR(i_dev->clk); - - if (prepare) - return clk_prepare_enable(i_dev->clk); - - clk_disable_unprepare(i_dev->clk); - return 0; } static void dw_i2c_set_fifo_size(struct dw_i2c_dev *dev, int id) @@ -356,7 +333,7 @@ static int dw_i2c_plat_probe(struct platform_device *pdev) i2c_dw_configure_master(dev); dev->clk = devm_clk_get(&pdev->dev, NULL); - if (!i2c_dw_plat_prepare_clk(dev, true)) { + if (!i2c_dw_prepare_clk(dev, true)) { dev->get_clk_rate_khz = i2c_dw_get_clk_rate_khz; if (!dev->sda_hold_time && ht) @@ -472,7 +449,7 @@ static int dw_i2c_plat_suspend(struct device *dev) struct dw_i2c_dev *i_dev = dev_get_drvdata(dev); i_dev->disable(i_dev); - i2c_dw_plat_prepare_clk(i_dev, false); + i2c_dw_prepare_clk(i_dev, false); return 0; } @@ -481,7 +458,7 @@ static int dw_i2c_plat_resume(struct device *dev) { struct dw_i2c_dev *i_dev = dev_get_drvdata(dev); - i2c_dw_plat_prepare_clk(i_dev, true); + i2c_dw_prepare_clk(i_dev, true); i_dev->init(i_dev); return 0; diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c index ea9578ab19a1..d42558d1b002 100644 --- a/drivers/i2c/busses/i2c-designware-slave.c +++ b/drivers/i2c/busses/i2c-designware-slave.c @@ -51,9 +51,7 @@ static void i2c_dw_configure_fifo_slave(struct dw_i2c_dev *dev) */ static int i2c_dw_init_slave(struct dw_i2c_dev *dev) { - u32 sda_falling_time, scl_falling_time; u32 reg, comp_param1; - u32 hcnt, lcnt; int ret; ret = i2c_dw_acquire_lock(dev); @@ -79,68 +77,6 @@ static int i2c_dw_init_slave(struct dw_i2c_dev *dev) /* Disable the adapter. */ __i2c_dw_enable_and_wait(dev, false); - /* Set standard and fast speed deviders for high/low periods. */ - sda_falling_time = dev->sda_falling_time ?: 300; /* ns */ - scl_falling_time = dev->scl_falling_time ?: 300; /* ns */ - - /* Set SCL timing parameters for standard-mode. */ - if (dev->ss_hcnt && dev->ss_lcnt) { - hcnt = dev->ss_hcnt; - lcnt = dev->ss_lcnt; - } else { - hcnt = i2c_dw_scl_hcnt(i2c_dw_clk_rate(dev), - 4000, /* tHD;STA = tHIGH = 4.0 us */ - sda_falling_time, - 0, /* 0: DW default, 1: Ideal */ - 0); /* No offset */ - lcnt = i2c_dw_scl_lcnt(i2c_dw_clk_rate(dev), - 4700, /* tLOW = 4.7 us */ - scl_falling_time, - 0); /* No offset */ - } - dw_writel(dev, hcnt, DW_IC_SS_SCL_HCNT); - dw_writel(dev, lcnt, DW_IC_SS_SCL_LCNT); - dev_dbg(dev->dev, "Standard-mode HCNT:LCNT = %d:%d\n", hcnt, lcnt); - - /* Set SCL timing parameters for fast-mode or fast-mode plus. */ - if ((dev->clk_freq == 1000000) && dev->fp_hcnt && dev->fp_lcnt) { - hcnt = dev->fp_hcnt; - lcnt = dev->fp_lcnt; - } else if (dev->fs_hcnt && dev->fs_lcnt) { - hcnt = dev->fs_hcnt; - lcnt = dev->fs_lcnt; - } else { - hcnt = i2c_dw_scl_hcnt(i2c_dw_clk_rate(dev), - 600, /* tHD;STA = tHIGH = 0.6 us */ - sda_falling_time, - 0, /* 0: DW default, 1: Ideal */ - 0); /* No offset */ - lcnt = i2c_dw_scl_lcnt(i2c_dw_clk_rate(dev), - 1300, /* tLOW = 1.3 us */ - scl_falling_time, - 0); /* No offset */ - } - dw_writel(dev, hcnt, DW_IC_FS_SCL_HCNT); - dw_writel(dev, lcnt, DW_IC_FS_SCL_LCNT); - dev_dbg(dev->dev, "Fast-mode HCNT:LCNT = %d:%d\n", hcnt, lcnt); - - if ((dev->slave_cfg & DW_IC_CON_SPEED_MASK) == - DW_IC_CON_SPEED_HIGH) { - if ((comp_param1 & DW_IC_COMP_PARAM_1_SPEED_MODE_MASK) - != DW_IC_COMP_PARAM_1_SPEED_MODE_HIGH) { - dev_err(dev->dev, "High Speed not supported!\n"); - dev->slave_cfg &= ~DW_IC_CON_SPEED_MASK; - dev->slave_cfg |= DW_IC_CON_SPEED_FAST; - } else if (dev->hs_hcnt && dev->hs_lcnt) { - hcnt = dev->hs_hcnt; - lcnt = dev->hs_lcnt; - dw_writel(dev, hcnt, DW_IC_HS_SCL_HCNT); - dw_writel(dev, lcnt, DW_IC_HS_SCL_LCNT); - dev_dbg(dev->dev, "HighSpeed-mode HCNT:LCNT = %d:%d\n", - hcnt, lcnt); - } - } - /* Configure SDA Hold Time if required. */ reg = dw_readl(dev, DW_IC_COMP_VERSION); if (reg >= DW_IC_SDA_HOLD_MIN_VERS) { diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c index 3855e0b11877..b02428498f6d 100644 --- a/drivers/i2c/busses/i2c-exynos5.c +++ b/drivers/i2c/busses/i2c-exynos5.c @@ -170,7 +170,7 @@ #define HSI2C_HS_TX_CLOCK 1000000 #define HSI2C_FS_TX_CLOCK 100000 -#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(1000)) +#define EXYNOS5_I2C_TIMEOUT (msecs_to_jiffies(100)) #define HSI2C_EXYNOS7 BIT(0) diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c index d80ea6ce91bb..58abb3eced58 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 }; /* @@ -34,7 +39,7 @@ static void i2c_gpio_setsda_val(void *data, int state) { struct i2c_gpio_private_data *priv = data; - gpiod_set_value(priv->sda, state); + gpiod_set_value_cansleep(priv->sda, state); } /* @@ -47,23 +52,125 @@ static void i2c_gpio_setscl_val(void *data, int state) { struct i2c_gpio_private_data *priv = data; - gpiod_set_value(priv->scl, state); + gpiod_set_value_cansleep(priv->scl, state); } static int i2c_gpio_getsda(void *data) { struct i2c_gpio_private_data *priv = data; - return gpiod_get_value(priv->sda); + return gpiod_get_value_cansleep(priv->sda); } static int i2c_gpio_getscl(void *data) { struct i2c_gpio_private_data *priv = data; - return gpiod_get_value(priv->scl); + return gpiod_get_value_cansleep(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) { @@ -179,6 +286,9 @@ static int i2c_gpio_probe(struct platform_device *pdev) if (IS_ERR(priv->scl)) return PTR_ERR(priv->scl); + if (gpiod_cansleep(priv->sda) || gpiod_cansleep(priv->scl)) + dev_warn(dev, "Slow GPIO pins might wreak havoc into I2C/SMBus bus timing"); + bit_data->setsda = i2c_gpio_setsda_val; bit_data->setscl = i2c_gpio_setscl_val; @@ -228,6 +338,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 +348,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; diff --git a/drivers/i2c/busses/i2c-imx-lpi2c.c b/drivers/i2c/busses/i2c-imx-lpi2c.c index e86801a63120..e6da2c7a9a3e 100644 --- a/drivers/i2c/busses/i2c-imx-lpi2c.c +++ b/drivers/i2c/busses/i2c-imx-lpi2c.c @@ -30,6 +30,7 @@ #include <linux/of_device.h> #include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/sched.h> #include <linux/slab.h> @@ -90,6 +91,8 @@ #define FAST_PLUS_MAX_BITRATE 3400000 #define HIGHSPEED_MAX_BITRATE 5000000 +#define I2C_PM_TIMEOUT 10 /* ms */ + enum lpi2c_imx_mode { STANDARD, /* 100+Kbps */ FAST, /* 400+Kbps */ @@ -274,8 +277,8 @@ static int lpi2c_imx_master_enable(struct lpi2c_imx_struct *lpi2c_imx) unsigned int temp; int ret; - ret = clk_enable(lpi2c_imx->clk); - if (ret) + ret = pm_runtime_get_sync(lpi2c_imx->adapter.dev.parent); + if (ret < 0) return ret; temp = MCR_RST; @@ -284,7 +287,7 @@ static int lpi2c_imx_master_enable(struct lpi2c_imx_struct *lpi2c_imx) ret = lpi2c_imx_config(lpi2c_imx); if (ret) - goto clk_disable; + goto rpm_put; temp = readl(lpi2c_imx->base + LPI2C_MCR); temp |= MCR_MEN; @@ -292,8 +295,9 @@ static int lpi2c_imx_master_enable(struct lpi2c_imx_struct *lpi2c_imx) return 0; -clk_disable: - clk_disable(lpi2c_imx->clk); +rpm_put: + pm_runtime_mark_last_busy(lpi2c_imx->adapter.dev.parent); + pm_runtime_put_autosuspend(lpi2c_imx->adapter.dev.parent); return ret; } @@ -306,7 +310,8 @@ static int lpi2c_imx_master_disable(struct lpi2c_imx_struct *lpi2c_imx) temp &= ~MCR_MEN; writel(temp, lpi2c_imx->base + LPI2C_MCR); - clk_disable(lpi2c_imx->clk); + pm_runtime_mark_last_busy(lpi2c_imx->adapter.dev.parent); + pm_runtime_put_autosuspend(lpi2c_imx->adapter.dev.parent); return 0; } @@ -606,22 +611,31 @@ static int lpi2c_imx_probe(struct platform_device *pdev) return ret; } + pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + temp = readl(lpi2c_imx->base + LPI2C_PARAM); lpi2c_imx->txfifosize = 1 << (temp & 0x0f); lpi2c_imx->rxfifosize = 1 << ((temp >> 8) & 0x0f); - clk_disable(lpi2c_imx->clk); - ret = i2c_add_adapter(&lpi2c_imx->adapter); if (ret) - goto clk_unprepare; + goto rpm_disable; + + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_put_autosuspend(&pdev->dev); dev_info(&lpi2c_imx->adapter.dev, "LPI2C adapter registered\n"); return 0; -clk_unprepare: - clk_unprepare(lpi2c_imx->clk); +rpm_disable: + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); |