mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-08 16:41:55 +00:00
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
This commit is contained in:
304
chip/lm4/i2c.c
304
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;
|
||||
}
|
||||
|
||||
/* 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, i;
|
||||
int started = start ? 0 : 1;
|
||||
uint32_t reg_mcs;
|
||||
|
||||
int i2c_read16(int port, int slave_addr, int offset, int* data)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return EC_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int i2c_read16(int port, int slave_addr, int offset, int *data)
|
||||
{
|
||||
int rv;
|
||||
int d;
|
||||
uint8_t reg, buf[2];
|
||||
|
||||
reg = offset & 0xff;
|
||||
/* I2C read 16-bit word:
|
||||
* Transmit 8-bit offset, and read 16bits
|
||||
*/
|
||||
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;
|
||||
|
||||
rv = wait_idle(port);
|
||||
if (rv) {
|
||||
mutex_unlock(port_mutex + port);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Send repeated start followed by receive */
|
||||
LM4_I2C_MSA(port) = (slave_addr & 0xff) | 0x01;
|
||||
LM4_I2C_MCS(port) = 0x0b;
|
||||
|
||||
rv = wait_idle(port);
|
||||
if (rv) {
|
||||
mutex_unlock(port_mutex + port);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Read the first byte */
|
||||
d = LM4_I2C_MDR(port) & 0xff;
|
||||
|
||||
/* Issue another read and then a stop. */
|
||||
LM4_I2C_MCS(port) = 0x05;
|
||||
|
||||
rv = wait_idle(port);
|
||||
if (rv) {
|
||||
mutex_unlock(port_mutex + port);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Read the second byte */
|
||||
if (slave_addr & I2C_FLAG_BIG_ENDIAN)
|
||||
*data = (d << 8) | (LM4_I2C_MDR(port) & 0xff);
|
||||
else
|
||||
*data = ((LM4_I2C_MDR(port) & 0xff) << 8) | d;
|
||||
rv = i2c_transmit_receive(port, slave_addr, ®, 1, buf, 2,
|
||||
START, STOP);
|
||||
mutex_unlock(port_mutex + port);
|
||||
|
||||
if (rv)
|
||||
return rv;
|
||||
|
||||
if (slave_addr & I2C_FLAG_BIG_ENDIAN)
|
||||
*data = ((int)buf[0] << 8) | buf[1];
|
||||
else
|
||||
*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];
|
||||
|
||||
buf[0] = offset & 0xff;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
rv = wait_idle(port);
|
||||
if (rv) {
|
||||
mutex_unlock(port_mutex + port);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
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;
|
||||
|
||||
reg = offset;
|
||||
|
||||
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;
|
||||
|
||||
rv = wait_idle(port);
|
||||
if (rv) {
|
||||
mutex_unlock(port_mutex + port);
|
||||
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;
|
||||
}
|
||||
|
||||
/* Read the byte */
|
||||
*data = LM4_I2C_MDR(port) & 0xff;
|
||||
|
||||
rv = i2c_transmit_receive(port, slave_addr, ®, 1, &val, 1,
|
||||
START, STOP);
|
||||
mutex_unlock(port_mutex + port);
|
||||
return EC_SUCCESS;
|
||||
|
||||
if (!rv)
|
||||
*data = val;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int i2c_write8(int port, int slave_addr, int offset, int data)
|
||||
{
|
||||
int rv;
|
||||
uint8_t buf[2];
|
||||
|
||||
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 rv;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user