[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