summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRong Chang <rongchang@chromium.org>2012-03-05 17:09:52 +0800
committerRong Chang <rongchang@chromium.org>2012-03-06 12:07:22 +0800
commita81f0cd5470975a9e56cc4909f97c32f7356aee0 (patch)
treed1aa1d80c2c66151fb54e2d70accd304111f51ee
parent89a8a082b13aa33d1352b94804fbe5885ea7fcf9 (diff)
downloadchrome-ec-a81f0cd5470975a9e56cc4909f97c32f7356aee0.tar.gz
Add I2C transmit/receive function
Implement a generalized I2C transmit-receive function that write-then-read blocks of raw data. Original 8-bit and 16-bit read/write functions are refactored. SMBus read-block protocol for ASCII string is also implemented based on this API. Signed-off-by: Rong Chang <rongchang@chromium.org> BUG=chrome-os-partner:8026,8316 TEST=manual: Type 'lightsaber' to check 8-bit read/write. Type 'charger' to check 16-bit read. Type 'charger input 4032' to check 16-bit write. Change-Id: I0ad3ad45b796d9ec03d8fbc1d643aa6a92d6343f
-rw-r--r--chip/lm4/i2c.c280
-rw-r--r--include/i2c.h12
2 files changed, 176 insertions, 116 deletions
diff --git a/chip/lm4/i2c.c b/chip/lm4/i2c.c
index c3cab94a57..1018d4a7c6 100644
--- a/chip/lm4/i2c.c
+++ b/chip/lm4/i2c.c
@@ -17,10 +17,21 @@
#define NUM_PORTS 6
+#define LM4_I2C_MCS_RUN (1 << 0)
+#define LM4_I2C_MCS_START (1 << 1)
+#define LM4_I2C_MCS_STOP (1 << 2)
+#define LM4_I2C_MCS_ACK (1 << 3)
+#define LM4_I2C_MCS_HS (1 << 4)
+#define LM4_I2C_MCS_QCMD (1 << 5)
+
+#define START 1
+#define STOP 1
+#define NO_START 0
+#define NO_STOP 0
+
static task_id_t task_waiting_on_port[NUM_PORTS];
static struct mutex port_mutex[NUM_PORTS];
-
static int wait_idle(int port)
{
int i;
@@ -47,56 +58,113 @@ static int wait_idle(int port)
return EC_SUCCESS;
}
-
-int i2c_read16(int port, int slave_addr, int offset, int* data)
+/* Transmit one block of raw data, then receive one block of raw data.
+ * <start> flag indicates this smbus session start from idle state.
+ * <stop> flag means this session can be termicate with smbus stop bit
+ */
+static int i2c_transmit_receive(int port, int slave_addr,
+ uint8_t *transmit_data, int transmit_size,
+ uint8_t *receive_data, int receive_size,
+ int start, int stop)
{
- int rv;
- int d;
-
- mutex_lock(port_mutex + port);
-
- *data = 0;
-
- /* Transmit the offset address to the slave; leave the master in
- * transmit state. */
- LM4_I2C_MSA(port) = slave_addr & 0xff;
- LM4_I2C_MDR(port) = offset & 0xff;
- LM4_I2C_MCS(port) = 0x03;
+ int rv, i;
+ int started = start ? 0 : 1;
+ uint32_t reg_mcs;
+
+ if (transmit_size == 0 && receive_size == 0)
+ return EC_SUCCESS;
+
+ if (transmit_data) {
+ LM4_I2C_MSA(port) = slave_addr & 0xff;
+ for (i = 0; i < transmit_size; i++) {
+ LM4_I2C_MDR(port) = transmit_data[i];
+ /* Setup master control/status register
+ * MCS sequence on multi-byte write:
+ * 0x3 0x1 0x1 ... 0x1 0x5
+ * Single byte write:
+ * 0x7
+ */
+ reg_mcs = LM4_I2C_MCS_RUN;
+ /* Set start bit on first byte */
+ if (!started) {
+ started = 1;
+ reg_mcs |= LM4_I2C_MCS_START;
+ }
+ /* Send stop bit if the stop flag is on,
+ * and caller doesn't expect to receive
+ * data.
+ */
+ if (stop && receive_size == 0 && i ==
+ (transmit_size - 1))
+ reg_mcs |= LM4_I2C_MCS_STOP;
+
+ LM4_I2C_MCS(port) = reg_mcs;
+
+ rv = wait_idle(port);
+ if (rv)
+ return rv;
+ }
+ }
- rv = wait_idle(port);
- if (rv) {
- mutex_unlock(port_mutex + port);
- return rv;
+ if (receive_size) {
+ if (transmit_size)
+ /* resend start bit when change direction */
+ started = 0;
+
+ LM4_I2C_MSA(port) = (slave_addr & 0xff) | 0x01;
+ for (i = 0; i < receive_size; i++) {
+ LM4_I2C_MDR(port) = receive_data[i];
+ /* MCS receive sequence on multi-byte read:
+ * 0xb 0x9 0x9 ... 0x9 0x5
+ * Single byte read:
+ * 0x7
+ */
+ reg_mcs = LM4_I2C_MCS_RUN;
+ if (!started) {
+ started = 1;
+ reg_mcs |= LM4_I2C_MCS_START;
+ }
+ /* ACK all bytes except the last one */
+ if (stop && i == (receive_size - 1))
+ reg_mcs |= LM4_I2C_MCS_STOP;
+ else
+ reg_mcs |= LM4_I2C_MCS_ACK;
+
+ LM4_I2C_MCS(port) = reg_mcs;
+ rv = wait_idle(port);
+ if (rv)
+ return rv;
+ receive_data[i] = LM4_I2C_MDR(port) & 0xff;
+ }
}
- /* Send repeated start followed by receive */
- LM4_I2C_MSA(port) = (slave_addr & 0xff) | 0x01;
- LM4_I2C_MCS(port) = 0x0b;
+ return EC_SUCCESS;
+}
+
- rv = wait_idle(port);
- if (rv) {
- mutex_unlock(port_mutex + port);
- return rv;
- }
- /* Read the first byte */
- d = LM4_I2C_MDR(port) & 0xff;
+int i2c_read16(int port, int slave_addr, int offset, int *data)
+{
+ int rv;
+ uint8_t reg, buf[2];
- /* Issue another read and then a stop. */
- LM4_I2C_MCS(port) = 0x05;
+ reg = offset & 0xff;
+ /* I2C read 16-bit word:
+ * Transmit 8-bit offset, and read 16bits
+ */
+ mutex_lock(port_mutex + port);
+ rv = i2c_transmit_receive(port, slave_addr, &reg, 1, buf, 2,
+ START, STOP);
+ mutex_unlock(port_mutex + port);
- rv = wait_idle(port);
- if (rv) {
- mutex_unlock(port_mutex + port);
+ if (rv)
return rv;
- }
- /* Read the second byte */
if (slave_addr & I2C_FLAG_BIG_ENDIAN)
- *data = (d << 8) | (LM4_I2C_MDR(port) & 0xff);
+ *data = ((int)buf[0] << 8) | buf[1];
else
- *data = ((LM4_I2C_MDR(port) & 0xff) << 8) | d;
- mutex_unlock(port_mutex + port);
+ *data = ((int)buf[1] << 8) | buf[0];
+
return EC_SUCCESS;
}
@@ -104,113 +172,93 @@ int i2c_read16(int port, int slave_addr, int offset, int* data)
int i2c_write16(int port, int slave_addr, int offset, int data)
{
int rv;
+ uint8_t buf[3];
- mutex_lock(port_mutex + port);
-
- /* Transmit the offset address to the slave; leave the master in
- * transmit state. */
- LM4_I2C_MDR(port) = offset & 0xff;
- LM4_I2C_MSA(port) = slave_addr & 0xff;
- LM4_I2C_MCS(port) = 0x03;
+ buf[0] = offset & 0xff;
- rv = wait_idle(port);
- if (rv) {
- mutex_unlock(port_mutex + port);
- return rv;
+ if (slave_addr & I2C_FLAG_BIG_ENDIAN) {
+ buf[1] = (data >> 8) & 0xff;
+ buf[2] = data & 0xff;
+ } else {
+ buf[1] = data & 0xff;
+ buf[2] = (data >> 8) & 0xff;
}
- /* Transmit the first byte */
- if (slave_addr & I2C_FLAG_BIG_ENDIAN)
- LM4_I2C_MDR(port) = (data >> 8) & 0xff;
- else
- LM4_I2C_MDR(port) = data & 0xff;
- LM4_I2C_MCS(port) = 0x01;
-
- rv = wait_idle(port);
- if (rv) {
- mutex_unlock(port_mutex + port);
- return rv;
- }
-
- /* Transmit the second byte and then a stop */
- if (slave_addr & I2C_FLAG_BIG_ENDIAN)
- LM4_I2C_MDR(port) = data & 0xff;
- else
- LM4_I2C_MDR(port) = (data >> 8) & 0xff;
- LM4_I2C_MCS(port) = 0x05;
-
+ mutex_lock(port_mutex + port);
+ rv = i2c_transmit_receive(port, slave_addr, buf, 3, 0, 0,
+ START, STOP);
mutex_unlock(port_mutex + port);
- return wait_idle(port);
-}
-/* TODO:(crosbug.com/p/8026) combine common functions to save space */
+ return rv;
+}
int i2c_read8(int port, int slave_addr, int offset, int* data)
{
int rv;
+ uint8_t reg, val;
- mutex_lock(port_mutex + port);
+ reg = offset;
- *data = 0;
+ mutex_lock(port_mutex + port);
+ rv = i2c_transmit_receive(port, slave_addr, &reg, 1, &val, 1,
+ START, STOP);
+ mutex_unlock(port_mutex + port);
- /* Transmit the offset address to the slave; leave the master in
- * transmit state. */
- LM4_I2C_MSA(port) = slave_addr & 0xff;
- LM4_I2C_MDR(port) = offset & 0xff;
- LM4_I2C_MCS(port) = 0x03;
+ if (!rv)
+ *data = val;
- rv = wait_idle(port);
- if (rv) {
- mutex_unlock(port_mutex + port);
- return rv;
- }
+ return rv;
+}
- /* Send repeated start followed by receive and stop */
- LM4_I2C_MSA(port) = (slave_addr & 0xff) | 0x01;
- LM4_I2C_MCS(port) = 0x07; /* NOTE: datasheet suggests 0x0b, but
- * 0x07 with the change in direction
- * flips it to a RECEIVE and STOP.
- * I think.
- */
- rv = wait_idle(port);
- if (rv) {
- mutex_unlock(port_mutex + port);
- return rv;
- }
+int i2c_write8(int port, int slave_addr, int offset, int data)
+{
+ int rv;
+ uint8_t buf[2];
- /* Read the byte */
- *data = LM4_I2C_MDR(port) & 0xff;
+ buf[0] = offset;
+ buf[1] = data;
+ mutex_lock(port_mutex + port);
+ rv = i2c_transmit_receive(port, slave_addr, buf, 2, 0, 0,
+ START, STOP);
mutex_unlock(port_mutex + port);
- return EC_SUCCESS;
+
+ return rv;
}
-int i2c_write8(int port, int slave_addr, int offset, int data)
+/* Read ascii string using smbus read block protocol.
+ * The return data <data> will be null terminated.
+ */
+int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data,
+ int len)
{
int rv;
+ uint8_t reg, block_length;
mutex_lock(port_mutex + port);
- /* Transmit the offset address to the slave; leave the master in
- * transmit state. */
- LM4_I2C_MDR(port) = offset & 0xff;
- LM4_I2C_MSA(port) = slave_addr & 0xff;
- LM4_I2C_MCS(port) = 0x03;
+ reg = offset;
+ /* Send device reg space offset, and read back block length.
+ * Keep this session open without a stop
+ */
+ rv = i2c_transmit_receive(port, slave_addr, &reg, 1, &block_length, 1,
+ START, NO_STOP);
+ if (rv)
+ goto exit;
- rv = wait_idle(port);
- if (rv) {
- mutex_unlock(port_mutex + port);
- return rv;
- }
+ if (len && block_length > (len - 1))
+ block_length = len - 1;
- /* Send repeated start followed by transmit and stop */
- LM4_I2C_MDR(port) = data & 0xff;
- LM4_I2C_MCS(port) = 0x05;
+ rv = i2c_transmit_receive(port, slave_addr, 0, 0, data, block_length,
+ NO_START, STOP);
+ data[block_length] = 0;
+exit:
mutex_unlock(port_mutex + port);
- return wait_idle(port);
+ return rv;
}
+
/*****************************************************************************/
/* Interrupt handlers */
@@ -265,7 +313,7 @@ static void scan_bus(int port, char *desc)
rv = wait_idle(port);
if (rv == EC_SUCCESS)
uart_printf("\nFound device at 8-bit addr 0x%02x\n", a);
- }
+}
mutex_unlock(port_mutex + port);
diff --git a/include/i2c.h b/include/i2c.h
index c3301e530d..701992e35e 100644
--- a/include/i2c.h
+++ b/include/i2c.h
@@ -36,4 +36,16 @@ int i2c_read8(int port, int slave_addr, int offset, int* data);
* space. */
int i2c_write8(int port, int slave_addr, int offset, int data);
+/* Read ascii string using smbus read block protocol.
+ * Read bytestream from <slaveaddr>:<offset> with format:
+ * [length_N] [byte_0] [byte_1] ... [byte_N-1]
+ *
+ * <len> : the max length of receving buffer. to read N bytes
+ * ascii, len should be at least N+1 to include the
+ * terminating 0.
+ * <len> == 0 : buffer size > 255
+ */
+int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data,
+ int len);
+
#endif /* __CROS_EC_I2C_H */