[v4,4/8] i2c: tegra: Support atomic transfers
diff mbox series

Message ID 20200112171430.27219-5-digetx@gmail.com
State Changes Requested
Headers show
Series
  • NVIDIA Tegra I2C driver fixes and improvements
Related show

Commit Message

Dmitry Osipenko Jan. 12, 2020, 5:14 p.m. UTC
System shutdown may happen with interrupts being disabled and in this case
I2C core rejects transfers if atomic transfer isn't supported by driver.

There were several occurrences where I found my Nexus 7 completely
discharged despite of being turned off and then one day I spotted this in
the log:

 reboot: Power down
 ------------[ cut here ]------------
 WARNING: CPU: 0 PID: 1 at drivers/i2c/i2c-core.h:40 i2c_transfer+0x95/0x9c
 No atomic I2C transfer handler for 'i2c-1'
 Modules linked in: tegra30_devfreq
 CPU: 0 PID: 1 Comm: systemd-shutdow Not tainted 5.4.0-next-20191202-00120-gf7ecd80fb803-dirty #3195
 Hardware name: NVIDIA Tegra SoC (Flattened Device Tree)
 [<c010e4b5>] (unwind_backtrace) from [<c010a0fd>] (show_stack+0x11/0x14)
 [<c010a0fd>] (show_stack) from [<c09995e5>] (dump_stack+0x85/0x94)
 [<c09995e5>] (dump_stack) from [<c011f3d1>] (__warn+0xc1/0xc4)
 [<c011f3d1>] (__warn) from [<c011f691>] (warn_slowpath_fmt+0x61/0x78)
 [<c011f691>] (warn_slowpath_fmt) from [<c069a8dd>] (i2c_transfer+0x95/0x9c)
 [<c069a8dd>] (i2c_transfer) from [<c05667f1>] (regmap_i2c_read+0x4d/0x6c)
 [<c05667f1>] (regmap_i2c_read) from [<c0563601>] (_regmap_raw_read+0x99/0x1cc)
 [<c0563601>] (_regmap_raw_read) from [<c0563757>] (_regmap_bus_read+0x23/0x38)
 [<c0563757>] (_regmap_bus_read) from [<c056293d>] (_regmap_read+0x3d/0xfc)
 [<c056293d>] (_regmap_read) from [<c0562d3b>] (_regmap_update_bits+0x87/0xc4)
 [<c0562d3b>] (_regmap_update_bits) from [<c0563add>] (regmap_update_bits_base+0x39/0x50)
 [<c0563add>] (regmap_update_bits_base) from [<c056fd39>] (max77620_pm_power_off+0x29/0x2c)
 [<c056fd39>] (max77620_pm_power_off) from [<c013bbdd>] (__do_sys_reboot+0xe9/0x170)
 [<c013bbdd>] (__do_sys_reboot) from [<c0101001>] (ret_fast_syscall+0x1/0x28)
 Exception stack(0xde907fa8 to 0xde907ff0)
 7fa0:                   00000000 00000000 fee1dead 28121969 4321fedc 00000000
 7fc0: 00000000 00000000 00000000 00000058 00000000 00000000 00000000 00000000
 7fe0: 0045adf0 bed9abb8 004444a0 b6c666d0
 ---[ end trace bdd18f87595b1a5e ]---

The atomic transferring is implemented by enforcing PIO mode for the
transfer and by polling interrupt status until transfer is completed or
failed.

Now system shuts down properly every time.

Tested-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
 drivers/i2c/busses/i2c-tegra.c | 84 ++++++++++++++++++++++++++++------
 1 file changed, 69 insertions(+), 15 deletions(-)

Comments

Wolfram Sang Jan. 13, 2020, 10:03 p.m. UTC | #1
On Sun, Jan 12, 2020 at 08:14:26PM +0300, Dmitry Osipenko wrote:
> System shutdown may happen with interrupts being disabled and in this case
> I2C core rejects transfers if atomic transfer isn't supported by driver.

Well, not quite. The core complains about it nowadays, but does not
reject messages. It will try the same behaviour as before. It will just
inform the user that somethings is tried which may not work. I probably
should update the error message printed saying that the transfer is
still tried.

> There were several occurrences where I found my Nexus 7 completely
> discharged despite of being turned off and then one day I spotted this in
> the log:

Given my reasoning above, that should have happened before the warning
was printed as well? Because same behaviour. I'd be surprised if there
was a change...
Dmitry Osipenko Jan. 13, 2020, 10:52 p.m. UTC | #2
14.01.2020 01:03, Wolfram Sang пишет:
> On Sun, Jan 12, 2020 at 08:14:26PM +0300, Dmitry Osipenko wrote:
>> System shutdown may happen with interrupts being disabled and in this case
>> I2C core rejects transfers if atomic transfer isn't supported by driver.
> 
> Well, not quite. The core complains about it nowadays, but does not
> reject messages. It will try the same behaviour as before. It will just
> inform the user that somethings is tried which may not work. I probably
> should update the error message printed saying that the transfer is
> still tried.

Indeed, now I'm recalling noticing that the transfer actually should
happen despite of the error message, but then completely forgot to
update the commit's message. I can update the message and send out v5,
if you're thinking that it's worthwhile to do.

>> There were several occurrences where I found my Nexus 7 completely
>> discharged despite of being turned off and then one day I spotted this in
>> the log:
> 
> Given my reasoning above, that should have happened before the warning
> was printed as well? Because same behaviour. I'd be surprised if there
> was a change...

Pretty sure that it was happening before, but I wasn't paying much
attention back then.
Dmitry Osipenko Jan. 14, 2020, 1:26 a.m. UTC | #3
14.01.2020 01:52, Dmitry Osipenko пишет:
> 14.01.2020 01:03, Wolfram Sang пишет:
>> On Sun, Jan 12, 2020 at 08:14:26PM +0300, Dmitry Osipenko wrote:
>>> System shutdown may happen with interrupts being disabled and in this case
>>> I2C core rejects transfers if atomic transfer isn't supported by driver.
>>
>> Well, not quite. The core complains about it nowadays, but does not
>> reject messages. It will try the same behaviour as before. It will just
>> inform the user that somethings is tried which may not work. I probably
>> should update the error message printed saying that the transfer is
>> still tried.
> 
> Indeed, now I'm recalling noticing that the transfer actually should
> happen despite of the error message, but then completely forgot to
> update the commit's message. I can update the message and send out v5,
> if you're thinking that it's worthwhile to do.

I have time to do it now, will send v5 shortly.

And yes, clarifying the error message could be helpful. Although,
perhaps it won't help much to drivers which may schedule during the
atomic transfer.

>>> There were several occurrences where I found my Nexus 7 completely
>>> discharged despite of being turned off and then one day I spotted this in
>>> the log:
>>
>> Given my reasoning above, that should have happened before the warning
>> was printed as well? Because same behaviour. I'd be surprised if there
>> was a change...
> 
> Pretty sure that it was happening before, but I wasn't paying much
> attention back then.
>
Wolfram Sang Jan. 14, 2020, 5:57 a.m. UTC | #4
> > Given my reasoning above, that should have happened before the warning
> > was printed as well? Because same behaviour. I'd be surprised if there
> > was a change...
> 
> Pretty sure that it was happening before, but I wasn't paying much
> attention back then.

Thanks! All good then. Because this WARN was added to report transfers
which might have never worked but went unnoticed so far. I am glad it
served its purpose.

Patch
diff mbox series

diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
index 882b283e0ed7..0245fc2b5684 100644
--- a/drivers/i2c/busses/i2c-tegra.c
+++ b/drivers/i2c/busses/i2c-tegra.c
@@ -18,6 +18,7 @@ 
 #include <linux/iopoll.h>
 #include <linux/irq.h>
 #include <linux/kernel.h>
+#include <linux/ktime.h>
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/pinctrl/consumer.h>
@@ -247,6 +248,7 @@  struct tegra_i2c_hw_feature {
  * @dma_buf_size: DMA buffer size
  * @is_curr_dma_xfer: indicates active DMA transfer
  * @dma_complete: DMA completion notifier
+ * @is_curr_atomic_xfer: indicates active atomic transfer
  */
 struct tegra_i2c_dev {
 	struct device *dev;
@@ -275,6 +277,7 @@  struct tegra_i2c_dev {
 	unsigned int dma_buf_size;
 	bool is_curr_dma_xfer;
 	struct completion dma_complete;
+	bool is_curr_atomic_xfer;
 };
 
 static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val,
@@ -683,7 +686,8 @@  static int tegra_i2c_wait_for_config_load(struct tegra_i2c_dev *i2c_dev)
 		reg_offset = tegra_i2c_reg_addr(i2c_dev, I2C_CONFIG_LOAD);
 		addr = i2c_dev->base + reg_offset;
 		i2c_writel(i2c_dev, I2C_MSTR_CONFIG_LOAD, I2C_CONFIG_LOAD);
-		if (in_interrupt())
+
+		if (i2c_dev->is_curr_atomic_xfer)
 			err = readl_poll_timeout_atomic(addr, val, val == 0,
 							1000,
 							I2C_CONFIG_LOAD_TIMEOUT);
@@ -983,6 +987,34 @@  static void tegra_i2c_config_fifo_trig(struct tegra_i2c_dev *i2c_dev,
 	i2c_writel(i2c_dev, val, reg);
 }
 
+static unsigned long
+tegra_i2c_poll_completion_timeout(struct tegra_i2c_dev *i2c_dev,
+				  struct completion *complete,
+				  unsigned int timeout_ms)
+{
+	ktime_t ktime = ktime_get();
+	ktime_t ktimeout = ktime_add_ms(ktime, timeout_ms);
+
+	do {
+		u32 status = i2c_readl(i2c_dev, I2C_INT_STATUS);
+
+		if (status) {
+			tegra_i2c_isr(i2c_dev->irq, i2c_dev);
+
+			if (completion_done(complete)) {
+				s64 delta = ktime_ms_delta(ktimeout, ktime);
+
+				return msecs_to_jiffies(delta) ?: 1;
+			}
+		}
+
+		ktime = ktime_get();
+
+	} while (ktime_before(ktime, ktimeout));
+
+	return 0;
+}
+
 static unsigned long
 tegra_i2c_wait_completion_timeout(struct tegra_i2c_dev *i2c_dev,
 				  struct completion *complete,
@@ -990,18 +1022,24 @@  tegra_i2c_wait_completion_timeout(struct tegra_i2c_dev *i2c_dev,
 {
 	unsigned long ret;
 
-	enable_irq(i2c_dev->irq);
-	ret = wait_for_completion_timeout(complete,
-					  msecs_to_jiffies(timeout_ms));
-	disable_irq(i2c_dev->irq);
+	if (i2c_dev->is_curr_atomic_xfer) {
+		ret = tegra_i2c_poll_completion_timeout(i2c_dev, complete,
+							timeout_ms);
+	} else {
+		enable_irq(i2c_dev->irq);
+		ret = wait_for_completion_timeout(complete,
+						  msecs_to_jiffies(timeout_ms));
+		disable_irq(i2c_dev->irq);
 
-	/*
-	 * There is a chance that completion may happen after IRQ
-	 * synchronization, which is done by disable_irq().
-	 */
-	if (ret == 0 && completion_done(complete)) {
-		dev_warn(i2c_dev->dev, "completion done after timeout\n");
-		ret = 1;
+		/*
+		 * There is a chance that completion may happen after IRQ
+		 * synchronization, which is done by disable_irq().
+		 */
+		if (ret == 0 && completion_done(complete)) {
+			dev_warn(i2c_dev->dev,
+				 "completion done after timeout\n");
+			ret = 1;
+		}
 	}
 
 	return ret;
@@ -1073,7 +1111,8 @@  static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
 
 	xfer_size = ALIGN(xfer_size, BYTES_PER_FIFO_WORD);
 	i2c_dev->is_curr_dma_xfer = (xfer_size > I2C_PIO_MODE_MAX_LEN) &&
-				    i2c_dev->dma_buf;
+				    i2c_dev->dma_buf &&
+				    !i2c_dev->is_curr_atomic_xfer;
 	tegra_i2c_config_fifo_trig(i2c_dev, xfer_size);
 	dma = i2c_dev->is_curr_dma_xfer;
 	/*
@@ -1271,6 +1310,19 @@  static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
 	return ret ?: i;
 }
 
+static int tegra_i2c_xfer_atomic(struct i2c_adapter *adap,
+				 struct i2c_msg msgs[], int num)
+{
+	struct tegra_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
+	int ret;
+
+	i2c_dev->is_curr_atomic_xfer = true;
+	ret = tegra_i2c_xfer(adap, msgs, num);
+	i2c_dev->is_curr_atomic_xfer = false;
+
+	return ret;
+}
+
 static u32 tegra_i2c_func(struct i2c_adapter *adap)
 {
 	struct tegra_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
@@ -1298,8 +1350,9 @@  static void tegra_i2c_parse_dt(struct tegra_i2c_dev *i2c_dev)
 }
 
 static const struct i2c_algorithm tegra_i2c_algo = {
-	.master_xfer	= tegra_i2c_xfer,
-	.functionality	= tegra_i2c_func,
+	.master_xfer		= tegra_i2c_xfer,
+	.master_xfer_atomic	= tegra_i2c_xfer_atomic,
+	.functionality		= tegra_i2c_func,
 };
 
 /* payload size is only 12 bit */
@@ -1607,6 +1660,7 @@  static int tegra_i2c_probe(struct platform_device *pdev)
 		goto unprepare_fast_clk;
 	}
 
+	pm_runtime_irq_safe(&pdev->dev);
 	pm_runtime_enable(&pdev->dev);
 	if (!pm_runtime_enabled(&pdev->dev)) {
 		ret = tegra_i2c_runtime_resume(&pdev->dev);