I want to read from i2c slave which need multi start operation to read its register values.
As up-to some extent I have traced I2C driver in Linux kernel 3.18.21, I found it do not support multi start operation and I have no way to read from this I2C slave (Power Over Ethernet manager PD69104B1).
I am still finding the way I can extended driver if needed for this i2c slave or anything else needed.
I use i2c-tools 3.2.1. I try to
$ i2cdump -y 0 0x20
but I can see same values which means it read first register every time.
$ i2cget -y 0 0x20 0x12
or any other register address returns the same value as a first register.
This slave support two read operation:
I try all possible ways:
i2c_smbus_access()
i2c_smbus_write_byte()
i2c_smbus_read_block_data()
write()
read()
but then most of the time i2c bus goes into timeout error and hang situations.
Anyone has idea how to achieve this in Linux?
This I2C slaves need unique Read cycles:
Change of Direction: S Addr Wr [A] RegAddress [A] S Addr Rd [A] [RegValue] P
Short Read: S Addr Rd [A] [RegValue] P
here last value returned from i2c slave do not expect ACK.
I tried to use I2C_M_NO_RD_ACK but with not much help. I read some value and then get FF.
This POE I2C slave have i2c time out of 14ms on SCL which is bit of doubt. This looks like i2c non standard as i2c can work on 0HZ i.e. SCL can be stretched by master as long as it want. Linux is definitely not real time OS so achieving this time out can not be guaranteed and i2c slave SCL timeout reset may happen. This is what my current conclusion is!
I2C Message notation used is from: https://www.kernel.org/doc/Documentation/i2c/i2c-protocol
why repeated start based i2c operation are not supported in linux?
As a matter of fact, they are supported.
If you are looking for a way to perform repeated start condition in user-space, you probably need to do ioctl()
with I2C_RDWR
request, like it's described here (see last code snippet in original question) and here (code in question).
Below described the way to perform repeated start in kernel-space.
In Linux kernel I2C read operations with repeated start condition are performed by default for combined (write/read) messages.
Here is an example how to perform combined I2C transfer:
/**
* Read set of registers via I2C using "repeated start" condition.
*
* Two I2C messages are being sent by this function:
* 1. I2C write operation (write register address) with no STOP bit in the end
* 2. I2C read operation
*
* @client: I2C client structure
* @reg: register address (subaddress)
* @len: bytes count to read
* @buf: buffer which will contain read data
*
* Returns 0 on success or negative value on error.
*/
static int i2c_read_regs(struct i2c_client *client, u8 reg, u8 len, u8 *buf)
{
int ret;
struct i2c_msg msg[2] = {
{
.addr = client->addr,
.len = 1,
.buf = ®,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = len,
.buf = buf,
}
};
ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 0) {
dev_err(&client->dev, "I2C read failed\n");
return ret;
}
return 0;
}
To read just 1 byte (single register value) you can use next helper function:
/**
* Read one register via I2C using "repeated start" condition.
*
* @client: I2C client structure
* @reg: register address (subaddress)
* @val: variable to store read value
*
* Returns 0 on success or negative value on error.
*/
static int i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
return i2c_read_regs(client, reg, 1, val);
}
Below is the illustration for i2c_read_regs(client, reg, 1, val)
call:
client->addr
reg
1
means that we want to read 1 byte of data (pink rectangle on picture)val
NOTE: If your I2C controller (or its driver) doesn't support repeated starts in combined messages, you still can use bit-bang implementation of I2C, which is i2c-gpio driver.
If nothing works, you can try next as a last resort. For some reason I can't quite remember, in order to make repeated start work I was needed to add I2C_M_NOSTART
to .flags
of first message, like this:
struct i2c_msg msg[2] = {
{
.addr = client->addr,
.flags = I2C_M_NOSTART,
.len = 1,
.buf = ®,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = len,
.buf = buf,
}
};
As noted in Documentation/i2c/i2c-protocol
:
If you set the
I2C_M_NOSTART
variable for the first partial message, we do not generateAddr
, but we do generate the startbitS
.
References:
[1] I2C on STLinux