diff mbox

i2c: designware: do not disable adapter after transfer

Message ID 1459478866-3896-1-git-send-email-lucas.de.marchi@gmail.com
State Superseded
Headers show

Commit Message

Lucas De Marchi April 1, 2016, 2:47 a.m. UTC
From: Lucas De Marchi <lucas.demarchi@intel.com>

Disabling the adapter after each transfer is pretty bad for sensors and
other devices doing small transfers at a high rate. It slows down the
transfer rate a lot since each of them have to wait the adapter to be
enabled again.

It was done in order to avoid the adapter to generate interrupts when
it's not being used. Instead of doing that here we just disable the
interrupt generation in the controller. With a small program test to
read/write registers in a sensor the speed doubled. Example below with
write sequences of 16 bytes:

Before:
	i2c-transfer-time -w -a 0x40 -x 6 -n 20000 -- 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07
	num_transfers=20000
	transfer_time_avg=1032.728500us

After:
	i2c-transfer-time -w -a 0x40 -x 6 -n 20000 -- 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07
	num_transfers=20000
	transfer_time_avg=470.256050us

During the init we check the status register for no activity and TX
buffer being empty since otherwise we can't change IC_TAR dynamically.

Signed-off-by: Lucas De Marchi <lucas.demarchi@intel.com>
---

Mika / Christian,

This is a second approach to a patch sent last year:
https://patchwork.ozlabs.org/patch/481952/

I hope that now it's in a better form. This has been tested on a Baytrail soc
(MinnowBoard Turbot) and is working. Would be nice to know if it also works on
Christian's machine since it was the one giving problems.  Christian, could you
give this a try?  It has been tested on top of 4.4.6 (+ some backports) and
4.6.0-rc1.

thanks!
Lucas De Marchi

 drivers/i2c/busses/i2c-designware-core.c | 50 ++++++++++++++++++++------------
 1 file changed, 31 insertions(+), 19 deletions(-)

Comments

Christian Ruppert April 7, 2016, 1:37 p.m. UTC | #1
Dear Lucas,

Sorry for the late reply but I had to put our test environment back
together to check this patch. I'll keep it around for a while in case
you have further iterations to test.

On 2016-04-01 04:47, Lucas De Marchi wrote:
> From: Lucas De Marchi <lucas.demarchi@intel.com>
> 
> Disabling the adapter after each transfer is pretty bad for sensors and
> other devices doing small transfers at a high rate. It slows down the
> transfer rate a lot since each of them have to wait the adapter to be
> enabled again.
> 
> It was done in order to avoid the adapter to generate interrupts when
> it's not being used. Instead of doing that here we just disable the
> interrupt generation in the controller. With a small program test to
> read/write registers in a sensor the speed doubled. Example below with
> write sequences of 16 bytes:
> 
> Before:
> 	i2c-transfer-time -w -a 0x40 -x 6 -n 20000 -- 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07
> 	num_transfers=20000
> 	transfer_time_avg=1032.728500us
> 
> After:
> 	i2c-transfer-time -w -a 0x40 -x 6 -n 20000 -- 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07
> 	num_transfers=20000
> 	transfer_time_avg=470.256050us
> 
> During the init we check the status register for no activity and TX
> buffer being empty since otherwise we can't change IC_TAR dynamically.
> 
> Signed-off-by: Lucas De Marchi <lucas.demarchi@intel.com>
> ---
> 
> Mika / Christian,
> 
> This is a second approach to a patch sent last year:
> https://patchwork.ozlabs.org/patch/481952/
> 
> I hope that now it's in a better form. This has been tested on a Baytrail soc
> (MinnowBoard Turbot) and is working. Would be nice to know if it also works on
> Christian's machine since it was the one giving problems.  Christian, could you
> give this a try?  It has been tested on top of 4.4.6 (+ some backports) and
> 4.6.0-rc1.

On Linux-4.6.0-rc2 the behaviour is still the same: The kernel locks up
in an irq loop at dwi2c driver probe time. If I don't apply the patch
everything works fine and the system boots and talks normally on the i2c
bus.

One solution might be to add a device-tree (and acpi) flag to tell the
driver that it does not need to expect any nastily behaved devices on
the bus. If this flag is given, the driver could use the faster
disable-interrupt strategy. Without the flag we need to fall back to the
conservative disable-i2c-controller strategy.

I think such a flag should be OK because I2C buses are generally quite
static and the list of devices should be known at system integration
time. In the rare cases where this is not true, users could still go
with the conservative approach. I know that the "cleaner" method would
be to rather mark nasty devices, either in device tree or in the driver,
but I guess the required changes in the I2C framework might be overkill
for this dwi2c specific problem.

Greetings,
  Christian

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Lucas De Marchi April 7, 2016, 5:28 p.m. UTC | #2
SGkgQ2hyaXN0aWFuLA0KDQpPbiBUaHUsIDIwMTYtMDQtMDcgYXQgMTU6MzcgKzAyMDAsIENocmlz
dGlhbiBSdXBwZXJ0IHdyb3RlOg0KPiBEZWFyIEx1Y2FzLA0KPiANCj4gU29ycnkgZm9yIHRoZSBs
YXRlIHJlcGx5IGJ1dCBJIGhhZCB0byBwdXQgb3VyIHRlc3QgZW52aXJvbm1lbnQgYmFjaw0KPiB0
b2dldGhlciB0byBjaGVjayB0aGlzIHBhdGNoLiBJJ2xsIGtlZXAgaXQgYXJvdW5kIGZvciBhIHdo
aWxlIGluIGNhc2UNCj4geW91IGhhdmUgZnVydGhlciBpdGVyYXRpb25zIHRvIHRlc3QuDQoNCm5w
LCBJJ2xsIHRyeSB0byBpdGVyYXRlIGZhc3RlciBvbiB0aGlzIHBhdGNoIG5vdywgdG9vLg0KDQo+
IE9uIExpbnV4LTQuNi4wLXJjMiB0aGUgYmVoYXZpb3VyIGlzIHN0aWxsIHRoZSBzYW1lOiBUaGUg
a2VybmVsIGxvY2tzIHVwDQo+IGluIGFuIGlycSBsb29wIGF0IGR3aTJjIGRyaXZlciBwcm9iZSB0
aW1lLiBJZiBJIGRvbid0IGFwcGx5IHRoZSBwYXRjaA0KPiBldmVyeXRoaW5nIHdvcmtzIGZpbmUg
YW5kIHRoZSBzeXN0ZW0gYm9vdHMgYW5kIHRhbGtzIG5vcm1hbGx5IG9uIHRoZSBpMmMNCj4gYnVz
Lg0KDQo6KA0KDQpJIHJlYWxseSBob3BlZCB0aGlzIHdvdWxkIHdvcmsgaW4geW91ciBjYXNlLg0K
DQo+IE9uZSBzb2x1dGlvbiBtaWdodCBiZSB0byBhZGQgYSBkZXZpY2UtdHJlZSAoYW5kIGFjcGkp
IGZsYWcgdG8gdGVsbCB0aGUNCj4gZHJpdmVyIHRoYXQgaXQgZG9lcyBub3QgbmVlZCB0byBleHBl
Y3QgYW55IG5hc3RpbHkgYmVoYXZlZCBkZXZpY2VzIG9uDQo+IHRoZSBidXMuIElmIHRoaXMgZmxh
ZyBpcyBnaXZlbiwgdGhlIGRyaXZlciBjb3VsZCB1c2UgdGhlIGZhc3Rlcg0KPiBkaXNhYmxlLWlu
dGVycnVwdCBzdHJhdGVneS4gV2l0aG91dCB0aGUgZmxhZyB3ZSBuZWVkIHRvIGZhbGwgYmFjayB0
byB0aGUNCj4gY29uc2VydmF0aXZlIGRpc2FibGUtaTJjLWNvbnRyb2xsZXIgc3RyYXRlZ3kuDQoN
CkknZCBsaWtlIHRvIHRyeSBzb21lIG90aGVyIGFwcHJvYWNoZXMgYmVmb3JlIHRoYXQuIFdoYXQg
aWYgd2Ugc3RhcnQgd2l0aCBpdA0KZGlzYWJsZWQgYW5kIGVuYWJsZSBhdCBmaXJzdCB1c2U/IEFm
dGVyIHRoYXQgd2Uga2VlcCB0aGUgYXBwcm9hY2ggb2YganVzdA0KZGlzYWJsaW5nIHRoZSBpbnRl
cnJ1cHRzLiDCoEkgY2FuIHByZXAgYSBwYXRjaCBmb3IgdGhhdC4NCg0KDQo+IEkgdGhpbmsgc3Vj
aCBhIGZsYWcgc2hvdWxkIGJlIE9LIGJlY2F1c2UgSTJDIGJ1c2VzIGFyZSBnZW5lcmFsbHkgcXVp
dGUNCj4gc3RhdGljIGFuZCB0aGUgbGlzdCBvZiBkZXZpY2VzIHNob3VsZCBiZSBrbm93biBhdCBz
eXN0ZW0gaW50ZWdyYXRpb24NCj4gdGltZS4gSW4gdGhlIHJhcmUgY2FzZXMgd2hlcmUgdGhpcyBp
cyBub3QgdHJ1ZSwgdXNlcnMgY291bGQgc3RpbGwgZ28NCj4gd2l0aCB0aGUgY29uc2VydmF0aXZl
IGFwcHJvYWNoLiBJIGtub3cgdGhhdCB0aGUgImNsZWFuZXIiIG1ldGhvZCB3b3VsZA0KPiBiZSB0
byByYXRoZXIgbWFyayBuYXN0eSBkZXZpY2VzLCBlaXRoZXIgaW4gZGV2aWNlIHRyZWUgb3IgaW4g
dGhlIGRyaXZlciwNCj4gYnV0IEkgZ3Vlc3MgdGhlIHJlcXVpcmVkIGNoYW5nZXMgaW4gdGhlIEky
QyBmcmFtZXdvcmsgbWlnaHQgYmUgb3ZlcmtpbGwNCj4gZm9yIHRoaXMgZHdpMmMgc3BlY2lmaWMg
cHJvYmxlbS4NCg0KYWdyZWVkDQoNCnRoYW5rcw0KTHVjYXMgRGUgTWFyY2hp
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jarkko Nikula April 8, 2016, 12:17 p.m. UTC | #3
Hi

On 04/01/2016 05:47 AM, Lucas De Marchi wrote:
> From: Lucas De Marchi <lucas.demarchi@intel.com>
>
> Disabling the adapter after each transfer is pretty bad for sensors and
> other devices doing small transfers at a high rate. It slows down the
> transfer rate a lot since each of them have to wait the adapter to be
> enabled again.
>
> It was done in order to avoid the adapter to generate interrupts when
> it's not being used. Instead of doing that here we just disable the
> interrupt generation in the controller. With a small program test to
> read/write registers in a sensor the speed doubled. Example below with
> write sequences of 16 bytes:
>
> Before:
> 	i2c-transfer-time -w -a 0x40 -x 6 -n 20000 -- 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07
> 	num_transfers=20000
> 	transfer_time_avg=1032.728500us
>
> After:
> 	i2c-transfer-time -w -a 0x40 -x 6 -n 20000 -- 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07 0 0 0xd0 0x07
> 	num_transfers=20000
> 	transfer_time_avg=470.256050us
>
I gave a test to this patch and saw similar improvements when dumping 
large set of registers from I2C connected audio codec.

echo Y > /sys/kernel/debug/regmap/i2c-10EC5640\:00/cache_bypass
time cat /sys/kernel/debug/regmap/i2c-10EC5640\:00/registers >/dev/null

I checked the runtime PM status of adapter was suspended before running 
above cat command in order to get comparable results.

Before patch time was ~0.55 - ~0.76 s and with patch applied time was 
~0.16 - ~0.25 s.

Hopefully we'll find how to prevent regression on Christian's machines.
Christian Ruppert April 8, 2016, 2:01 p.m. UTC | #4
On 2016-04-07 19:28, De Marchi, Lucas wrote:
> Hi Christian,
> 
> On Thu, 2016-04-07 at 15:37 +0200, Christian Ruppert wrote:
>> Dear Lucas,
>>
>> Sorry for the late reply but I had to put our test environment back
>> together to check this patch. I'll keep it around for a while in case
>> you have further iterations to test.
> 
> np, I'll try to iterate faster on this patch now, too.
> 
>> On Linux-4.6.0-rc2 the behaviour is still the same: The kernel locks up
>> in an irq loop at dwi2c driver probe time. If I don't apply the patch
>> everything works fine and the system boots and talks normally on the i2c
>> bus.
> 
> :(
> 
> I really hoped this would work in your case.
> 
>> One solution might be to add a device-tree (and acpi) flag to tell the
>> driver that it does not need to expect any nastily behaved devices on
>> the bus. If this flag is given, the driver could use the faster
>> disable-interrupt strategy. Without the flag we need to fall back to the
>> conservative disable-i2c-controller strategy.
> 
> I'd like to try some other approaches before that. What if we start with it
> disabled and enable at first use?

I think this might work in our case and be worth a try. When thinking
about it, it might even be cleaner to add a way to specify a list of
reset pins (e.g. through the GPIO reset framework) to trigger before
activating the bus. This would allow for more than one badly behaved
devices on the bus and also render everything more independent of the
probe order.

I'm afraid that the general case (bad device behaviour after the first
transfer) still cannot be covered by this strategy but I'm not sure if I
have a way to test this.

> After that we keep the approach of just
> disabling the interrupts.  I can prep a patch for that.

OK, I'll give it a try when it's ready.

Greetings,
  Christian

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/i2c/busses/i2c-designware-core.c b/drivers/i2c/busses/i2c-designware-core.c
index 99b54be..f8e6f2b 100644
--- a/drivers/i2c/busses/i2c-designware-core.c
+++ b/drivers/i2c/busses/i2c-designware-core.c
@@ -90,6 +90,7 @@ 
 					 DW_IC_INTR_STOP_DET)
 
 #define DW_IC_STATUS_ACTIVITY	0x1
+#define DW_IC_STATUS_TX_EMPTY	0x2
 
 #define DW_IC_ERR_TX_ABRT	0x1
 
@@ -383,6 +384,8 @@  int i2c_dw_init(struct dw_i2c_dev *dev)
 	/* configure the i2c master */
 	dw_writel(dev, dev->master_cfg , DW_IC_CON);
 
+	__i2c_dw_enable(dev, true);
+
 	if (dev->release_lock)
 		dev->release_lock(dev);
 	return 0;
@@ -412,9 +415,16 @@  static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
 {
 	struct i2c_msg *msgs = dev->msgs;
 	u32 ic_con, ic_tar = 0;
-
-	/* Disable the adapter */
-	__i2c_dw_enable(dev, false);
+	bool need_reenable = false;
+	u32 ic_status;
+
+	/* check ic_tar and ic_con can be dynamically updated */
+	ic_status = dw_readl(dev, DW_IC_STATUS);
+	if (ic_status & DW_IC_STATUS_ACTIVITY
+		|| !(ic_status & DW_IC_STATUS_TX_EMPTY)) {
+		need_reenable = true;
+		__i2c_dw_enable(dev, false);
+	}
 
 	/* if the slave address is ten bit address, enable 10BITADDR */
 	ic_con = dw_readl(dev, DW_IC_CON);
@@ -442,8 +452,8 @@  static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
 	/* enforce disabled interrupts (due to HW issues) */
 	i2c_dw_disable_int(dev);
 
-	/* Enable the adapter */
-	__i2c_dw_enable(dev, true);
+	if (need_reenable)
+		__i2c_dw_enable(dev, true);
 
 	/* Clear and enable interrupts */
 	dw_readl(dev, DW_IC_CLR_INTR);
@@ -624,7 +634,8 @@  static int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev)
 }
 
 /*
- * Prepare controller for a transaction and call i2c_dw_xfer_msg
+ * Prepare controller for a transaction and start transfer by calling
+ * i2c_dw_xfer_init()
  */
 static int
 i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
@@ -665,22 +676,11 @@  i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
 	/* wait for tx to complete */
 	if (!wait_for_completion_timeout(&dev->cmd_complete, HZ)) {
 		dev_err(dev->dev, "controller timed out\n");
-		/* i2c_dw_init implicitly disables the adapter */
 		i2c_dw_init(dev);
 		ret = -ETIMEDOUT;
 		goto done;
 	}
 
-	/*
-	 * We must disable the adapter before returning and signaling the end
-	 * of the current transfer. Otherwise the hardware might continue
-	 * generating interrupts which in turn causes a race condition with
-	 * the following transfer.  Needs some more investigation if the
-	 * additional interrupts are a hardware bug or this driver doesn't
-	 * handle them correctly yet.
-	 */
-	__i2c_dw_enable(dev, false);
-
 	if (dev->msg_err) {
 		ret = dev->msg_err;
 		goto done;
@@ -818,9 +818,21 @@  static irqreturn_t i2c_dw_isr(int this_irq, void *dev_id)
 	 */
 
 tx_aborted:
-	if ((stat & (DW_IC_INTR_TX_ABRT | DW_IC_INTR_STOP_DET)) || dev->msg_err)
+	if ((stat & (DW_IC_INTR_TX_ABRT | DW_IC_INTR_STOP_DET))
+			|| dev->msg_err) {
+		/*
+		 * We must disable interruts before returning and signaling
+		 * the end of the current transfer. Otherwise the hardware
+		 * might continue generating interrupts which in turn causes a
+		 * race condition with next transfer.  Needs some more
+		 * investigation if the additional interrupts are a hardware
+		 * bug or this driver doesn't handle them correctly yet.
+		 */
+		i2c_dw_disable_int(dev);
+		dw_readl(dev, DW_IC_CLR_INTR);
+
 		complete(&dev->cmd_complete);
-	else if (unlikely(dev->accessor_flags & ACCESS_INTR_MASK)) {
+	} else if (unlikely(dev->accessor_flags & ACCESS_INTR_MASK)) {
 		/* workaround to trigger pending interrupt */
 		stat = dw_readl(dev, DW_IC_INTR_MASK);
 		i2c_dw_disable_int(dev);