diff options
author | Rong Chang <rongchang@chromium.org> | 2012-03-05 17:09:52 +0800 |
---|---|---|
committer | Rong Chang <rongchang@chromium.org> | 2012-03-06 12:07:22 +0800 |
commit | a81f0cd5470975a9e56cc4909f97c32f7356aee0 (patch) | |
tree | d1aa1d80c2c66151fb54e2d70accd304111f51ee /chip/lm4/i2c.c | |
parent | 89a8a082b13aa33d1352b94804fbe5885ea7fcf9 (diff) | |
download | chrome-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
Diffstat (limited to 'chip/lm4/i2c.c')
-rw-r--r-- | chip/lm4/i2c.c | 280 |
1 files changed, 164 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, ®, 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, ®, 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, ®, 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); |