[PATCH 4/4] i2c: cadence: Recover bus after controller reset
From: shubhrajyoti . datta
Date: Mon Dec 09 2019 - 05:41:16 EST
From: Chirag Parekh <chiragp@xxxxxxxxxx>
This will save from potential lock-up caused when I2c master controller
resets in the middle of transfer and the slave is holding SDA line to
transmit more data.
Signed-off-by: Chirag Parekh <chiragp@xxxxxxxxxx>
Signed-off-by: Michal Simek <michal.simek@xxxxxxxxxx>
Signed-off-by: Nava kishore Manne <navam@xxxxxxxxxx>
Signed-off-by: Shubhrajyoti Datta <shubhrajyoti.datta@xxxxxxxxxx>
---
drivers/i2c/busses/i2c-cadence.c | 101 +++++++++++++++++++++++++++++++++++++++
1 file changed, 101 insertions(+)
diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c
index 8a2983e..e11e986 100644
--- a/drivers/i2c/busses/i2c-cadence.c
+++ b/drivers/i2c/busses/i2c-cadence.c
@@ -7,13 +7,16 @@
#include <linux/clk.h>
#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
+#include <linux/of_gpio.h>
#include <linux/pm_runtime.h>
+#include <linux/pinctrl/consumer.h>
/* Register offsets for the I2C device. */
#define CDNS_I2C_CR_OFFSET 0x00 /* Control Register, RW */
@@ -139,6 +142,10 @@
* @clk_rate_change_nb: Notifier block for clock rate changes
* @quirks: flag for broken hold bit usage in r1p10
* @ctrl_reg: Cached value of the control register.
+ * @rinfo: Structure holding recovery information.
+ * @pinctrl: Pin control state holder.
+ * @pinctrl_pins_default: Default pin control state.
+ * @pinctrl_pins_gpio: GPIO pin control state.
*/
struct cdns_i2c {
struct device *dev;
@@ -160,6 +167,10 @@ struct cdns_i2c {
struct notifier_block clk_rate_change_nb;
u32 quirks;
u32 ctrl_reg;
+ struct i2c_bus_recovery_info rinfo;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pinctrl_pins_default;
+ struct pinctrl_state *pinctrl_pins_gpio;
};
struct cdns_platform_data {
@@ -544,6 +555,7 @@ static int cdns_i2c_process_msg(struct cdns_i2c *id, struct i2c_msg *msg,
/* Wait for the signal of completion */
time_left = wait_for_completion_timeout(&id->xfer_done, adap->timeout);
if (time_left == 0) {
+ i2c_recover_bus(adap);
cdns_i2c_master_reset(adap);
dev_err(id->adap.dev.parent,
"timeout waiting on completion\n");
@@ -880,6 +892,88 @@ static int __maybe_unused cdns_i2c_runtime_resume(struct device *dev)
return 0;
}
+/**
+ * cdns_i2c_prepare_recovery - Withhold recovery state
+ * @adapter: Pointer to i2c adapter
+ *
+ * This function is called to prepare for recovery.
+ * It changes the state of pins from SCL/SDA to GPIO.
+ */
+static void cdns_i2c_prepare_recovery(struct i2c_adapter *adapter)
+{
+ struct cdns_i2c *p_cdns_i2c;
+
+ p_cdns_i2c = container_of(adapter, struct cdns_i2c, adap);
+
+ /* Setting pin state as gpio */
+ pinctrl_select_state(p_cdns_i2c->pinctrl,
+ p_cdns_i2c->pinctrl_pins_gpio);
+}
+
+/**
+ * cdns_i2c_unprepare_recovery - Release recovery state
+ * @adapter: Pointer to i2c adapter
+ *
+ * This function is called on exiting recovery. It reverts
+ * the state of pins from GPIO to SCL/SDA.
+ */
+static void cdns_i2c_unprepare_recovery(struct i2c_adapter *adapter)
+{
+ struct cdns_i2c *p_cdns_i2c;
+
+ p_cdns_i2c = container_of(adapter, struct cdns_i2c, adap);
+
+ /* Setting pin state to default(i2c) */
+ pinctrl_select_state(p_cdns_i2c->pinctrl,
+ p_cdns_i2c->pinctrl_pins_default);
+}
+
+/**
+ * cdns_i2c_init_recovery_info - Initialize I2C bus recovery
+ * @pid: Pointer to cdns i2c structure
+ * @pdev: Handle to the platform device structure
+ *
+ * This function does required initialization for i2c bus
+ * recovery. It registers three functions for prepare,
+ * recover and unprepare
+ *
+ * Return: 0 on Success, negative error otherwise.
+ */
+static int cdns_i2c_init_recovery_info(struct cdns_i2c *pid,
+ struct platform_device *pdev)
+{
+ struct i2c_bus_recovery_info *rinfo = &pid->rinfo;
+
+ pid->pinctrl_pins_default = pinctrl_lookup_state(pid->pinctrl,
+ PINCTRL_STATE_DEFAULT);
+ pid->pinctrl_pins_gpio = pinctrl_lookup_state(pid->pinctrl, "gpio");
+
+ /* Fetches GPIO pins */
+ rinfo->sda_gpiod = devm_gpiod_get(&pdev->dev, "sda-gpios", 0);
+ rinfo->scl_gpiod = devm_gpiod_get(&pdev->dev, "scl-gpios", 0);
+
+ /* if GPIO driver isn't ready yet, deffer probe */
+ if (PTR_ERR(rinfo->sda_gpiod) == -EPROBE_DEFER ||
+ PTR_ERR(rinfo->scl_gpiod) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ /* Validates fetched information */
+ if (IS_ERR(rinfo->sda_gpiod) ||
+ IS_ERR(rinfo->scl_gpiod) ||
+ IS_ERR(pid->pinctrl_pins_default) ||
+ IS_ERR(pid->pinctrl_pins_gpio)) {
+ dev_dbg(&pdev->dev, "recovery information incomplete\n");
+ return 0;
+ }
+
+ rinfo->prepare_recovery = cdns_i2c_prepare_recovery;
+ rinfo->unprepare_recovery = cdns_i2c_unprepare_recovery;
+ rinfo->recover_bus = i2c_generic_scl_recovery;
+ pid->adap.bus_recovery_info = rinfo;
+
+ return 0;
+}
+
static const struct dev_pm_ops cdns_i2c_dev_pm_ops = {
SET_RUNTIME_PM_OPS(cdns_i2c_runtime_suspend,
cdns_i2c_runtime_resume, NULL)
@@ -926,6 +1020,13 @@ static int cdns_i2c_probe(struct platform_device *pdev)
id->quirks = data->quirks;
}
+ id->pinctrl = devm_pinctrl_get(&pdev->dev);
+ if (!IS_ERR(id->pinctrl)) {
+ ret = cdns_i2c_init_recovery_info(id, pdev);
+ if (ret)
+ return ret;
+ }
+
r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
id->membase = devm_ioremap_resource(&pdev->dev, r_mem);
if (IS_ERR(id->membase))
--
2.1.1