diff options
Diffstat (limited to 'target/linux/brcm2708/patches-4.9/950-0110-i2c-bcm2835-Add-support-for-Repeated-Start-Condition.patch')
-rw-r--r-- | target/linux/brcm2708/patches-4.9/950-0110-i2c-bcm2835-Add-support-for-Repeated-Start-Condition.patch | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/target/linux/brcm2708/patches-4.9/950-0110-i2c-bcm2835-Add-support-for-Repeated-Start-Condition.patch b/target/linux/brcm2708/patches-4.9/950-0110-i2c-bcm2835-Add-support-for-Repeated-Start-Condition.patch new file mode 100644 index 0000000..0cd5aa4 --- /dev/null +++ b/target/linux/brcm2708/patches-4.9/950-0110-i2c-bcm2835-Add-support-for-Repeated-Start-Condition.patch @@ -0,0 +1,181 @@ +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) |