[linux-yocto] [PATCH 17/18] i2c-axxia: Support I2C_M_RECV_LEN
Charlie Paul
cpaul.windriver at gmail.com
Tue Feb 18 09:26:47 PST 2014
From: Anders Berg <anders.berg at lsi.com>
Add support for the I2C_M_RECV_LEN flag to enable SMBus block data transfers.
This patch also fixes an issue where the master failed to perform a proper
repeated start, but instead issued a stop followed by a start. This behaviour
confused some SMBus slave devices.
The STOP-START was caused by the use of the automatic mode of the controller.
By using manual mode the driver gets control over when a STOP condition is
signalled.
Signed-off-by: Anders Berg <anders.berg at lsi.com>
---
drivers/i2c/busses/i2c-axxia.c | 220 +++++++++++++++++++++++++++-------------
1 file changed, 151 insertions(+), 69 deletions(-)
diff --git a/drivers/i2c/busses/i2c-axxia.c b/drivers/i2c/busses/i2c-axxia.c
index 4f86418..e0a4b24 100644
--- a/drivers/i2c/busses/i2c-axxia.c
+++ b/drivers/i2c/busses/i2c-axxia.c
@@ -27,7 +27,8 @@
#include <linux/module.h>
#define SCL_WAIT_TIMEOUT_NS 25000000
-#define I2C_TIMEOUT (msecs_to_jiffies(1000))
+#define I2C_XFER_TIMEOUT (msecs_to_jiffies(500))
+#define I2C_STOP_TIMEOUT (msecs_to_jiffies(100))
#define TX_FIFO_SIZE 8
#define RX_FIFO_SIZE 8
@@ -123,12 +124,10 @@ struct axxia_i2c_dev {
int irq;
/* xfer completion object */
struct completion msg_complete;
- /* pointer to current message data */
- u8 *msg_buf;
- /* size of unsent data in the message buffer */
- size_t msg_buf_remaining;
- /* identifies read transfers */
- int msg_read;
+ /* pointer to current message */
+ struct i2c_msg *msg;
+ /* number of bytes transferred in msg */
+ size_t msg_xfrd;
/* error code for completed message */
int msg_err;
/* current i2c bus clock rate */
@@ -168,10 +167,21 @@ axxia_i2c_init(struct axxia_i2c_dev *idev)
u32 t_setup;
u32 tmo_clk;
u32 prescale;
+ unsigned long timeout;
dev_dbg(idev->dev, "rate=%uHz per_clk=%uMHz -> ratio=1:%u\n",
idev->bus_clk_rate, clk_mhz, divisor);
+ /* Reset controller */
+ writel(0x01, &idev->regs->soft_reset);
+ timeout = jiffies + msecs_to_jiffies(100);
+ while (readl(&idev->regs->soft_reset) & 1) {
+ if (time_after(jiffies, timeout)) {
+ dev_warn(idev->dev, "Soft reset failed\n");
+ break;
+ }
+ }
+
/* Enable Master Mode */
writel(0x1, &idev->regs->global_control);
@@ -186,8 +196,8 @@ axxia_i2c_init(struct axxia_i2c_dev *idev)
/* SDA Setup Time */
writel(t_setup, &idev->regs->sda_setup_time);
- /* SDA Hold Time, 5ns */
- writel(ns_to_clk(5, clk_mhz), &idev->regs->sda_hold_time);
+ /* SDA Hold Time, 300ns */
+ writel(ns_to_clk(300, clk_mhz), &idev->regs->sda_hold_time);
/* Filter <50ns spikes */
writel(ns_to_clk(50, clk_mhz), &idev->regs->spike_fltr_len);
@@ -195,15 +205,16 @@ axxia_i2c_init(struct axxia_i2c_dev *idev)
tmo_clk = ns_to_clk(SCL_WAIT_TIMEOUT_NS, clk_mhz);
/*
- * Find the prescaler value that makes tmo_clk fit in 15-bits counter.
+ Find the prescaler value that makes tmo_clk fit in 15-bits counter.
*/
- for (prescale = 0; prescale < 15; ++prescale) {
+ for (prescale=0; prescale < 15; ++prescale) {
if (tmo_clk <= 0x7fff)
break;
tmo_clk >>= 1;
}
- if (tmo_clk > 0x7fff)
+ if (tmo_clk > 0x7fff) {
tmo_clk = 0x7fff;
+ }
/* Prescale divider (log2) */
writel(prescale, &idev->regs->timer_clock_div);
@@ -228,15 +239,40 @@ axxia_i2c_init(struct axxia_i2c_dev *idev)
}
static int
-axxia_i2c_empty_rx_fifo(struct axxia_i2c_dev *idev)
+i2c_m_rd(const struct i2c_msg *msg)
{
- size_t rx_fifo_avail = readl(&idev->regs->mst_rx_fifo);
- int bytes_to_transfer = min(rx_fifo_avail, idev->msg_buf_remaining);
+ return (msg->flags & I2C_M_RD) != 0;
+}
- idev->msg_buf_remaining -= bytes_to_transfer;
+static int
+i2c_m_recv_len(const struct i2c_msg *msg)
+{
+ return (msg->flags & I2C_M_RECV_LEN) != 0;
+}
- while (0 < bytes_to_transfer--)
- *idev->msg_buf++ = readl(&idev->regs->mst_data);
+static int
+axxia_i2c_empty_rx_fifo(struct axxia_i2c_dev *idev)
+{
+ struct i2c_msg *msg = idev->msg;
+ size_t rx_fifo_avail = readl(&idev->regs->mst_rx_fifo);
+ int bytes_to_transfer = min(rx_fifo_avail, msg->len - idev->msg_xfrd);
+
+ while (0 < bytes_to_transfer--) {
+ int c = readl(&idev->regs->mst_data);
+ if (idev->msg_xfrd == 0 && i2c_m_recv_len(msg)) {
+ if (c == 0 || c > I2C_SMBUS_BLOCK_MAX) {
+ idev->msg_err = -EPROTO;
+ i2c_int_disable(idev, ~0);
+ dev_err(idev->dev,
+ "invalid SMBus block size (%d)\n", c);
+ complete(&idev->msg_complete);
+ break;
+ }
+ msg->len += c;
+ writel(msg->len, &idev->regs->mst_rx_xfer);
+ }
+ msg->buf[idev->msg_xfrd++] = c;
+ }
return 0;
}
@@ -244,17 +280,44 @@ axxia_i2c_empty_rx_fifo(struct axxia_i2c_dev *idev)
static int
axxia_i2c_fill_tx_fifo(struct axxia_i2c_dev *idev)
{
+ struct i2c_msg *msg = idev->msg;
size_t tx_fifo_avail = TX_FIFO_SIZE - readl(&idev->regs->mst_tx_fifo);
- int bytes_to_transfer = min(tx_fifo_avail, idev->msg_buf_remaining);
-
- idev->msg_buf_remaining -= bytes_to_transfer;
+ int bytes_to_transfer = min(tx_fifo_avail, msg->len - idev->msg_xfrd);
while (0 < bytes_to_transfer--)
- writel(*idev->msg_buf++, &idev->regs->mst_data);
+ writel(msg->buf[idev->msg_xfrd++], &idev->regs->mst_data);
return 0;
}
+static char *
+status_str(u32 status)
+{
+ static char buf[128];
+
+ buf[0] = '\0';
+
+ if (status & MST_STATUS_RFL)
+ strcat(buf, "RFL ");
+ if (status & MST_STATUS_TFL)
+ strcat(buf, "TFL ");
+ if (status & MST_STATUS_SNS)
+ strcat(buf, "SNS ");
+ if (status & MST_STATUS_SS)
+ strcat(buf, "SS ");
+ if (status & MST_STATUS_SCC)
+ strcat(buf, "SCC ");
+ if (status & MST_STATUS_TSS)
+ strcat(buf, "TSS ");
+ if (status & MST_STATUS_AL)
+ strcat(buf, "AL ");
+ if (status & MST_STATUS_ND)
+ strcat(buf, "ND ");
+ if (status & MST_STATUS_NA)
+ strcat(buf, "NA ");
+ return buf;
+}
+
static irqreturn_t
axxia_i2c_isr(int irq, void *_dev)
{
@@ -264,11 +327,11 @@ axxia_i2c_isr(int irq, void *_dev)
/* Clear interrupt */
writel(0x01, &idev->regs->interrupt_status);
- if (status & MST_STATUS_ERR) {
+ if (unlikely(status & MST_STATUS_ERR)) {
idev->msg_err = status & MST_STATUS_ERR;
i2c_int_disable(idev, ~0);
- dev_err(idev->dev, "error %#x, rx=%u/%u tx=%u/%u\n",
- idev->msg_err,
+ dev_err(idev->dev, "error %s, rx=%u/%u tx=%u/%u\n",
+ status_str(idev->msg_err),
readl(&idev->regs->mst_rx_bytes_xfrd),
readl(&idev->regs->mst_rx_xfer),
readl(&idev->regs->mst_tx_bytes_xfrd),
@@ -277,24 +340,28 @@ axxia_i2c_isr(int irq, void *_dev)
return IRQ_HANDLED;
}
+ /* Stop completed? */
+ if (status & MST_STATUS_SCC) {
+ i2c_int_disable(idev, ~0);
+ complete(&idev->msg_complete);
+ }
+
/* Transfer done? */
if (status & (MST_STATUS_SNS | MST_STATUS_SS)) {
- if (idev->msg_read && idev->msg_buf_remaining > 0)
+ if (i2c_m_rd(idev->msg) && idev->msg_xfrd < idev->msg->len)
axxia_i2c_empty_rx_fifo(idev);
- WARN_ON(idev->msg_buf_remaining > 0);
i2c_int_disable(idev, ~0);
complete(&idev->msg_complete);
}
/* RX FIFO needs service? */
- if (idev->msg_read && (status & MST_STATUS_RFL)) {
- WARN_ON(idev->msg_buf_remaining == 0);
+ if (i2c_m_rd(idev->msg) && (status & MST_STATUS_RFL)) {
axxia_i2c_empty_rx_fifo(idev);
}
/* TX FIFO needs service? */
- if (!idev->msg_read && (status & MST_STATUS_TFL)) {
- if (idev->msg_buf_remaining)
+ if (!i2c_m_rd(idev->msg) && (status & MST_STATUS_TFL)) {
+ if (idev->msg_xfrd < idev->msg->len)
axxia_i2c_fill_tx_fifo(idev);
else
i2c_int_disable(idev, MST_STATUS_TFL);
@@ -303,80 +370,91 @@ axxia_i2c_isr(int irq, void *_dev)
return IRQ_HANDLED;
}
+
static int
-axxia_i2c_xfer_msg(struct axxia_i2c_dev *idev, struct i2c_msg *msg, int stop)
+axxia_i2c_xfer_msg(struct axxia_i2c_dev *idev, struct i2c_msg *msg)
{
- u32 int_mask;
+ u32 int_mask = MST_STATUS_ERR | MST_STATUS_SNS;
int ret;
- dev_dbg(idev->dev, "xfer_msg: chip=%#x, buffer=[%02x %02x %02x %02x], len=%d, stop=%d\n",
- msg->addr, msg->buf[0], msg->buf[1], msg->buf[2], msg->buf[3],
- msg->len, stop);
-
if (msg->len == 0 || msg->len > 255)
return -EINVAL;
- idev->msg_buf = msg->buf;
- idev->msg_buf_remaining = msg->len;
- idev->msg_err = 0;
- idev->msg_read = (msg->flags & I2C_M_RD);
+ idev->msg = msg;
+ idev->msg_xfrd = 0;
+ idev->msg_err = 0;
INIT_COMPLETION(idev->msg_complete);
- if (msg->flags & I2C_M_RD) {
+ if (i2c_m_rd(msg)) {
/* TX 0 bytes */
writel(0, &idev->regs->mst_tx_xfer);
/* RX # bytes */
writel(msg->len, &idev->regs->mst_rx_xfer);
/* Chip address for write */
- writel(CHIP_READ(msg->addr & 0xfe), &idev->regs->mst_addr_1);
+ writel(CHIP_READ(msg->addr), &idev->regs->mst_addr_1);
} else {
/* TX # bytes */
writel(msg->len, &idev->regs->mst_tx_xfer);
/* RX 0 bytes */
writel(0, &idev->regs->mst_rx_xfer);
/* Chip address for write */
- writel(CHIP_WRITE(msg->addr & 0xfe), &idev->regs->mst_addr_1);
+ writel(CHIP_WRITE(msg->addr), &idev->regs->mst_addr_1);
}
writel(msg->addr >> 8, &idev->regs->mst_addr_2);
- if (!(msg->flags & I2C_M_RD))
- axxia_i2c_fill_tx_fifo(idev);
-
- int_mask = MST_STATUS_ERR;
- int_mask |= stop ? MST_STATUS_SS : MST_STATUS_SNS;
- if (msg->flags & I2C_M_RD)
+ if (i2c_m_rd(msg)) {
int_mask |= MST_STATUS_RFL;
- else if (idev->msg_buf_remaining)
- int_mask |= MST_STATUS_TFL;
+ } else {
+ axxia_i2c_fill_tx_fifo(idev);
+ if (idev->msg_xfrd < msg->len)
+ int_mask |= MST_STATUS_TFL;
+ }
/* Start manual mode */
- writel(stop ? 0x9 : 0x8, &idev->regs->mst_command);
+ writel(0x8, &idev->regs->mst_command);
i2c_int_enable(idev, int_mask);
- ret = wait_for_completion_timeout(&idev->msg_complete, I2C_TIMEOUT);
+ ret = wait_for_completion_timeout(&idev->msg_complete, I2C_XFER_TIMEOUT);
i2c_int_disable(idev, int_mask);
+ WARN_ON(readl(&idev->regs->mst_command) & 0x8);
+
if (WARN_ON(ret == 0)) {
- dev_warn(idev->dev, "i2c transfer timed out\n");
- /* Reset i2c controller and re-initialize */
- writel(0x01, &idev->regs->soft_reset);
- while (readl(&idev->regs->soft_reset) & 1)
- cpu_relax();
+ dev_warn(idev->dev, "xfer timeout (%#x)\n", msg->addr);
axxia_i2c_init(idev);
return -ETIMEDOUT;
}
- WARN_ON(readl(&idev->regs->mst_command) & 0x8);
+ if (unlikely(idev->msg_err != 0)) {
+ axxia_i2c_init(idev);
+ return -EIO;
+ }
+
+ return 0;
+}
- dev_dbg(idev->dev, "transfer complete: %d %d %#x\n",
- ret, completion_done(&idev->msg_complete), idev->msg_err);
+static int
+axxia_i2c_stop(struct axxia_i2c_dev *idev)
+{
+ u32 int_mask = MST_STATUS_ERR | MST_STATUS_SCC;
+ int ret;
- if (likely(idev->msg_err == 0))
- return 0;
+ INIT_COMPLETION(idev->msg_complete);
- return -EIO;
+ /* Issue stop */
+ writel(0xb, &idev->regs->mst_command);
+ i2c_int_enable(idev, int_mask);
+ ret = wait_for_completion_timeout(&idev->msg_complete, I2C_STOP_TIMEOUT);
+ i2c_int_disable(idev, int_mask);
+ if (ret == 0) {
+ return -ETIMEDOUT;
+ }
+
+ WARN_ON(readl(&idev->regs->mst_command) & 0x8);
+
+ return 0;
}
static int
@@ -387,17 +465,21 @@ axxia_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
int ret = 0;
for (i = 0; ret == 0 && i < num; i++) {
- int stop = (i == num-1);
- ret = axxia_i2c_xfer_msg(idev, &msgs[i], stop);
+ ret = axxia_i2c_xfer_msg(idev, &msgs[i]);
}
+ axxia_i2c_stop(idev);
+
return ret ?: i;
}
static u32
axxia_i2c_func(struct i2c_adapter *adap)
{
- return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_EMUL;
+ return (I2C_FUNC_I2C |
+ I2C_FUNC_10BIT_ADDR |
+ I2C_FUNC_SMBUS_EMUL |
+ I2C_FUNC_SMBUS_BLOCK_DATA);
}
@@ -445,7 +527,7 @@ axxia_i2c_probe(struct platform_device *pdev)
}
idev->base = base;
- idev->regs = (struct __iomem i2c_regs*)base;
+ idev->regs = (struct __iomem i2c_regs *) base;
idev->i2c_clk = i2c_clk;
idev->dev = &pdev->dev;
init_completion(&idev->msg_complete);
--
1.7.9.5
More information about the linux-yocto
mailing list