mirror of https://github.com/hak5/openwrt-owl.git
182 lines
5.6 KiB
Diff
182 lines
5.6 KiB
Diff
From db656eb11aebb0d7e4b833f9b452503ddb1351f1 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= <noralf@tronnes.org>
|
|
Date: Fri, 23 Sep 2016 04:54:27 +0200
|
|
Subject: [PATCH] i2c: bcm2835: Add support for Repeated Start Condition
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
Documentation/i2c/i2c-protocol states that Combined transactions should
|
|
separate messages with a Start bit and end the whole transaction with a
|
|
Stop bit. This patch adds support for issuing only a Start between
|
|
messages instead of a Stop followed by a Start.
|
|
|
|
This implementation differs from downstream i2c-bcm2708 in 2 respects:
|
|
- it uses an interrupt to detect that the transfer is active instead
|
|
of using polling. There is no interrupt for Transfer Active, but by
|
|
not prefilling the FIFO it's possible to use the TXW interrupt.
|
|
- when resetting/disabling the controller between transfers it writes
|
|
CLEAR to the control register instead of just zero.
|
|
Using just zero gave many errors. This might be the reason why
|
|
downstream had to disable this feature and make it available with a
|
|
module parameter.
|
|
|
|
I have run thousands of transfers to a DS1307 (rtc), MMA8451 (accel)
|
|
and AT24C32 (eeprom) in parallel without problems.
|
|
|
|
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
|
|
Acked-by: Eric Anholt <eric@anholt.net>
|
|
---
|
|
drivers/i2c/busses/i2c-bcm2835.c | 101 ++++++++++++++++++++++++---------------
|
|
1 file changed, 63 insertions(+), 38 deletions(-)
|
|
|
|
--- a/drivers/i2c/busses/i2c-bcm2835.c
|
|
+++ b/drivers/i2c/busses/i2c-bcm2835.c
|
|
@@ -63,6 +63,7 @@ struct bcm2835_i2c_dev {
|
|
struct i2c_adapter adapter;
|
|
struct completion completion;
|
|
struct i2c_msg *curr_msg;
|
|
+ int num_msgs;
|
|
u32 msg_err;
|
|
u8 *msg_buf;
|
|
size_t msg_buf_remaining;
|
|
@@ -110,6 +111,45 @@ static void bcm2835_drain_rxfifo(struct
|
|
}
|
|
|
|
/*
|
|
+ * Repeated Start Condition (Sr)
|
|
+ * The BCM2835 ARM Peripherals datasheet mentions a way to trigger a Sr when it
|
|
+ * talks about reading from a slave with 10 bit address. This is achieved by
|
|
+ * issuing a write, poll the I2CS.TA flag and wait for it to be set, and then
|
|
+ * issue a read.
|
|
+ * A comment in https://github.com/raspberrypi/linux/issues/254 shows how the
|
|
+ * firmware actually does it using polling and says that it's a workaround for
|
|
+ * a problem in the state machine.
|
|
+ * It turns out that it is possible to use the TXW interrupt to know when the
|
|
+ * transfer is active, provided the FIFO has not been prefilled.
|
|
+ */
|
|
+
|
|
+static void bcm2835_i2c_start_transfer(struct bcm2835_i2c_dev *i2c_dev)
|
|
+{
|
|
+ u32 c = BCM2835_I2C_C_ST | BCM2835_I2C_C_I2CEN;
|
|
+ struct i2c_msg *msg = i2c_dev->curr_msg;
|
|
+ bool last_msg = (i2c_dev->num_msgs == 1);
|
|
+
|
|
+ if (!i2c_dev->num_msgs)
|
|
+ return;
|
|
+
|
|
+ i2c_dev->num_msgs--;
|
|
+ i2c_dev->msg_buf = msg->buf;
|
|
+ i2c_dev->msg_buf_remaining = msg->len;
|
|
+
|
|
+ if (msg->flags & I2C_M_RD)
|
|
+ c |= BCM2835_I2C_C_READ | BCM2835_I2C_C_INTR;
|
|
+ else
|
|
+ c |= BCM2835_I2C_C_INTT;
|
|
+
|
|
+ if (last_msg)
|
|
+ c |= BCM2835_I2C_C_INTD;
|
|
+
|
|
+ bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_A, msg->addr);
|
|
+ bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_DLEN, msg->len);
|
|
+ bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, c);
|
|
+}
|
|
+
|
|
+/*
|
|
* Note about I2C_C_CLEAR on error:
|
|
* The I2C_C_CLEAR on errors will take some time to resolve -- if you were in
|
|
* non-idle state and I2C_C_READ, it sets an abort_rx flag and runs through
|
|
@@ -151,6 +191,12 @@ static irqreturn_t bcm2835_i2c_isr(int t
|
|
}
|
|
|
|
bcm2835_fill_txfifo(i2c_dev);
|
|
+
|
|
+ if (i2c_dev->num_msgs && !i2c_dev->msg_buf_remaining) {
|
|
+ i2c_dev->curr_msg++;
|
|
+ bcm2835_i2c_start_transfer(i2c_dev);
|
|
+ }
|
|
+
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
@@ -175,30 +221,25 @@ complete:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
-static int bcm2835_i2c_xfer_msg(struct bcm2835_i2c_dev *i2c_dev,
|
|
- struct i2c_msg *msg)
|
|
+static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
|
|
+ int num)
|
|
{
|
|
- u32 c;
|
|
+ struct bcm2835_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
|
|
unsigned long time_left;
|
|
+ int i;
|
|
|
|
- i2c_dev->curr_msg = msg;
|
|
- i2c_dev->msg_buf = msg->buf;
|
|
- i2c_dev->msg_buf_remaining = msg->len;
|
|
- reinit_completion(&i2c_dev->completion);
|
|
-
|
|
- bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, BCM2835_I2C_C_CLEAR);
|
|
+ for (i = 0; i < (num - 1); i++)
|
|
+ if (msgs[i].flags & I2C_M_RD) {
|
|
+ dev_warn_once(i2c_dev->dev,
|
|
+ "only one read message supported, has to be last\n");
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
|
|
- if (msg->flags & I2C_M_RD) {
|
|
- c = BCM2835_I2C_C_READ | BCM2835_I2C_C_INTR;
|
|
- } else {
|
|
- c = BCM2835_I2C_C_INTT;
|
|
- bcm2835_fill_txfifo(i2c_dev);
|
|
- }
|
|
- c |= BCM2835_I2C_C_ST | BCM2835_I2C_C_INTD | BCM2835_I2C_C_I2CEN;
|
|
+ i2c_dev->curr_msg = msgs;
|
|
+ i2c_dev->num_msgs = num;
|
|
+ reinit_completion(&i2c_dev->completion);
|
|
|
|
- bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_A, msg->addr);
|
|
- bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_DLEN, msg->len);
|
|
- bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, c);
|
|
+ bcm2835_i2c_start_transfer(i2c_dev);
|
|
|
|
time_left = wait_for_completion_timeout(&i2c_dev->completion,
|
|
BCM2835_I2C_TIMEOUT);
|
|
@@ -209,31 +250,15 @@ static int bcm2835_i2c_xfer_msg(struct b
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
- if (likely(!i2c_dev->msg_err))
|
|
- return 0;
|
|
+ if (!i2c_dev->msg_err)
|
|
+ return num;
|
|
|
|
dev_dbg(i2c_dev->dev, "i2c transfer failed: %x\n", i2c_dev->msg_err);
|
|
|
|
if (i2c_dev->msg_err & BCM2835_I2C_S_ERR)
|
|
return -EREMOTEIO;
|
|
- else
|
|
- return -EIO;
|
|
-}
|
|
-
|
|
-static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
|
|
- int num)
|
|
-{
|
|
- struct bcm2835_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
|
|
- int i;
|
|
- int ret = 0;
|
|
-
|
|
- for (i = 0; i < num; i++) {
|
|
- ret = bcm2835_i2c_xfer_msg(i2c_dev, &msgs[i]);
|
|
- if (ret)
|
|
- break;
|
|
- }
|
|
|
|
- return ret ?: i;
|
|
+ return -EIO;
|
|
}
|
|
|
|
static u32 bcm2835_i2c_func(struct i2c_adapter *adap)
|