[RFC linux dev-4.7 3/5] drivers: i2c: Add transfer implementation for FSI algorithm

Eddie James eajames at linux.vnet.ibm.com
Sat Mar 11 08:48:24 AEDT 2017


From: "Edward A. James" <eajames at us.ibm.com>

Use polling instead of interrupts; we have no hardware IRQ over FSI.

Signed-off-by: Edward A. James <eajames at us.ibm.com>
---
 drivers/i2c/busses/i2c-fsi.c | 226 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 225 insertions(+), 1 deletion(-)

diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c
index c47cd234..ebdfa72 100644
--- a/drivers/i2c/busses/i2c-fsi.c
+++ b/drivers/i2c/busses/i2c-fsi.c
@@ -132,6 +132,7 @@ struct fsi_i2c_bus {
 	struct i2c_adapter	adapter;
 	struct fsi_device	*fsi;
 	u8			fifo_size;
+	u16			xfrd;
 	unsigned long		clk;
 };
 
@@ -182,10 +183,233 @@ static int fsi_i2c_dev_init(struct fsi_i2c_bus *i2c)
 	return rc;
 }
 
+static int fsi_i2c_set_port(struct fsi_device *dev, int port)
+{
+	int rc;
+	u32 mode, dummy = 0;
+	int old_port;
+
+	rc = fsi_device_read(dev, I2C_BOE_MODE, &mode, sizeof(mode));
+	if (rc)
+		return rc;
+
+	old_port = GETFIELD(I2C_BOE_MODE_PORT, mode);
+
+	if (old_port != port) {
+		mode = SETFIELD(I2C_BOE_MODE_PORT, mode, port);
+		rc = fsi_device_write(dev, I2C_BOE_MODE, &mode,
+				      sizeof(mode));
+		if (rc)
+			return rc;
+
+		/* reset engine when port is changed */
+		rc = fsi_device_write(dev, I2C_BOE_RESET_ERR, &dummy,
+				      sizeof(dummy));
+		if (rc)
+			return rc;
+	}
+
+	return rc;
+}
+
+static int fsi_i2c_start(struct fsi_i2c_bus *i2c, struct i2c_msg *msg,
+			 bool stop)
+{
+	int rc;
+	u32 cmd = I2C_BOE_CMD_WITH_START | I2C_BOE_CMD_WITH_ADDR;
+
+	i2c->xfrd = 0;
+
+	if (msg->flags & I2C_M_RD)
+		cmd |= I2C_BOE_CMD_READ;
+
+	if (stop || msg->flags & I2C_M_STOP)
+		cmd |= I2C_BOE_CMD_WITH_STOP;
+
+	cmd = SETFIELD(I2C_BOE_CMD_ADDR, cmd, msg->addr >> 1);
+	cmd = SETFIELD(I2C_BOE_CMD_LEN, cmd, msg->len);
+
+	rc = fsi_device_write(i2c->fsi, I2C_BOE_CMD, &cmd, sizeof(cmd));
+
+	return rc;
+}
+
+static int fsi_i2c_write_fifo(struct fsi_i2c_bus *i2c, struct i2c_msg *msg,
+			      u8 fifo_count)
+{
+	int write;
+	int rc = 0;
+	int bytes_to_write = i2c->fifo_size - fifo_count;
+	int bytes_remaining = msg->len - i2c->xfrd;
+
+	if (bytes_to_write > bytes_remaining)
+		bytes_to_write = bytes_remaining;
+
+	while (bytes_to_write > 0) {
+		write = bytes_to_write;
+		/* fsi limited to 4 byte ops */
+		if (bytes_to_write > 4)
+			write = 4;
+
+		rc = fsi_device_write(i2c->fsi, I2C_BOE_FIFO,
+				      &msg->buf[i2c->xfrd], write);
+		if (rc)
+			return rc;
+
+		i2c->xfrd += write;
+		bytes_to_write -= write;
+	}
+
+	return rc;
+}
+
+static int fsi_i2c_read_fifo(struct fsi_i2c_bus *i2c, struct i2c_msg *msg,
+			     u8 fifo_count)
+{
+	int read;
+	int rc = 0;
+	int xfr_remaining = msg->len - i2c->xfrd;
+	u32 dummy;
+
+	while (fifo_count) {
+		read = fifo_count;
+		/* fsi limited to 4 byte ops */
+		if (fifo_count > 4)
+			read = 4;
+
+		if (xfr_remaining) {
+			if (xfr_remaining < read)
+				read = xfr_remaining;
+
+			rc = fsi_device_read(i2c->fsi, I2C_BOE_FIFO,
+					     &msg->buf[i2c->xfrd], read);
+			if (rc)
+				return rc;
+
+			i2c->xfrd += read;
+			xfr_remaining -= read;
+		} else {
+			/* no more buffer but data in fifo, need to clear it */
+			rc = fsi_device_read(i2c->fsi, I2C_BOE_FIFO,
+					     &dummy, read);
+			if (rc)
+				return rc;
+		}
+
+		fifo_count -= read;
+	}
+
+	return rc;
+}
+
+static int fsi_i2c_handle_status(struct fsi_i2c_bus *i2c, struct i2c_msg *msg,
+				 u32 status)
+{
+	int rc;
+	u8 fifo_count;
+	u32 dummy = 0;
+
+	if (status & I2C_BOE_STAT_ERR) {
+		rc = fsi_device_write(i2c->fsi, I2C_BOE_RESET_ERR, &dummy,
+				      sizeof(dummy));
+		if (rc)
+			return rc;
+
+		if (status & I2C_BOE_STAT_NACK)
+			return -EFAULT;
+
+		return -EIO;
+	}
+
+	if (status & I2C_BOE_STAT_DAT_REQ) {
+		fifo_count = GETFIELD(I2C_BOE_STAT_FIFO_COUNT, status);
+
+		if (msg->flags & I2C_M_RD)
+			rc = fsi_i2c_read_fifo(i2c, msg, fifo_count);
+		else
+			rc = fsi_i2c_write_fifo(i2c, msg, fifo_count);
+
+		return rc;
+	}
+
+	if (status & I2C_BOE_STAT_CMD_COMP) {
+		if (i2c->xfrd < msg->len)
+			rc = -ENODATA;
+		else
+			rc = msg->len;
+	}
+
+	return rc;
+}
+
+static int fsi_i2c_wait(struct fsi_i2c_bus *i2c, struct i2c_msg *msg,
+			unsigned long timeout)
+{
+	const unsigned long local_timeout = 2; /* jiffies */
+	u32 status = 0;
+	int rc;
+
+	do {
+		rc = fsi_device_read(i2c->fsi, I2C_BOE_STAT, &status,
+				     sizeof(status));
+		if (rc)
+			return rc;
+
+		if (status & I2C_BOE_STAT_ANY_RESP) {
+			rc = fsi_i2c_handle_status(i2c, msg, status);
+			if (rc < 0)
+				return rc;
+
+			/* cmd complete and all data xfrd */
+			if (rc == msg->len)
+				return 0;
+
+			/* need to xfr more data, but maybe don't need wait */
+			continue;
+		}
+
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(local_timeout);
+		timeout = (timeout < local_timeout) ? 0 :
+			timeout - local_timeout;
+	} while (timeout);
+
+	return -ETIME;
+}
+
 static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
 			int num)
 {
-	return -ENOSYS;
+	int i;
+	int rc = 0;
+	int port;
+	unsigned long start_time;
+	struct fsi_i2c_bus *i2c = adap->algo_data;
+	struct i2c_msg *msg;
+
+	if (num) {
+		/* TODO: how else to get port information? this works though */
+		port = GETFIELD(I2C_ADDR_PORT, msgs[0].addr);
+		rc = fsi_i2c_set_port(i2c->fsi, port);
+		if (rc)
+			return rc;
+	}
+
+	for (i = 0; i < num; ++i) {
+		msg = msgs + i;
+		start_time = jiffies;
+
+		rc = fsi_i2c_start(i2c, msg, i == num - 1);
+		if (rc)
+			return rc;
+
+		rc = fsi_i2c_wait(i2c, msg,
+				  adap->timeout - (jiffies - start_time));
+		if (rc)
+			return rc;
+	}
+
+	return rc;
 }
 
 static u32 fsi_i2c_functionality(struct i2c_adapter *adap)
-- 
1.8.3.1



More information about the openbmc mailing list