I am writing code for implementing a simple i2c
read/write function using the general linux i2c
driver linux/i2c-dev.h
I am confused about the ioctl
: I2C_SLAVE
The kernel documentation states as follows :
You can do plain i2c transactions by using read(2) and write(2) calls. You do not need to pass the address byte; instead, set it through ioctl I2C_SLAVE before you try to access the device
However I am using the ioctl I2C_RDWR
where I again set the slave address using i2c_msg.addr
.
The kernel documentation also mentions the following :
Some ioctl() calls are for administrative tasks and are handled by i2c-dev directly. Examples include I2C_SLAVE
So is it must to use the ioctl I2C_SLAVE
? If so do I need to set it just once or every time I perform a read and write?
If I had an i2c
device I could have just tested the code on the device and would not have bothered you guys but unfortunately I don't have one right now.
Thanks for the help.
There are three major methods of communicating with i2c devices from userspace.
1. IOCTL I2C_RDWR
This method allows for simultaneous read/write and sending an uninterrupted sequence of message. Not all i2c devices support this method.
Before performing i/o with this method, you should check whether the device supports this method using an ioctl I2C_FUNCS
operation.
Using this method, you do not need to perform an ioctl I2C_SLAVE
operation -- it is done behind the scenes using the information embedded in the messages.
2. IOCTL SMBUS
This method of i/o is more powerful but the resulting code is more verbose. This method can be used if the device does not support the I2C_RDWR
method.
Using this method, you do need to perform an ioctl I2C_SLAVE
operation (or, if the device is busy, an I2C_SLAVE_FORCE
operation).
3. SYSFS I/O
This method uses the basic file i/o system calls read()
and write()
. Uninterrupted sequential operations are not possible using this method. This method can be used if the device does not support the I2C_RDWR
method.
Using this method, you do need to perform an ioctl I2C_SLAVE
operation (or, if the device is busy, an I2C_SLAVE_FORCE
operation).
I can't think of any situation when this method would be preferable to others, unless you need the chip to be treated like a file.
Full IOCTL Example
I haven't tested this example, but it shows the conceptual flow of writing to an i2c device.-- automatically detecting whether to use the ioctl I2C_RDWR
or smbus technique.
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <errno.h> #include <string.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/i2c.h> #include <linux/i2c-dev.h> #include <sys/ioctl.h> #define I2C_ADAPTER "/dev/i2c-0" #define I2C_DEVICE 0x00 int i2c_ioctl_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data) { int i, j = 0; int ret; uint8_t *buf; buf = malloc(1 + 2 * (sizeof(data) / sizeof(data[0]))); if (buf == NULL) { return -ENOMEM; } buf[j ++] = regaddr; for (i = 0; i < (sizeof(data) / sizeof(data[0])); i ++) { buf[j ++] = (data[i] & 0xff00) >> 8; buf[j ++] = data[i] & 0xff; } struct i2c_msg messages[] = { { .addr = dev, .buf = buf, .len = sizeof(buf) / sizeof(buf[0]), }, }; struct i2c_rdwr_ioctl_data payload = { .msgs = messages, .nmsgs = sizeof(messages) / sizeof(messages[0]), }; ret = ioctl(fd, I2C_RDWR, &payload); if (ret < 0) { ret = -errno; } free (buf); return ret; } int i2c_ioctl_smbus_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data) { int i, j = 0; int ret; uint8_t *buf; buf = malloc(2 * (sizeof(data) / sizeof(data[0]))); if (buf == NULL) { return -ENOMEM; } for (i = 0; i < (sizeof(data) / sizeof(data[0])); i ++) { buf[j ++] = (data[i] & 0xff00) >> 8; buf[j ++] = data[i] & 0xff; } struct i2c_smbus_ioctl_data payload = { .read_write = I2C_SMBUS_WRITE, .size = I2C_SMBUS_WORD_DATA, .command = regaddr, .data = (void *) buf, }; ret = ioctl (fd, I2C_SLAVE_FORCE, dev); if (ret < 0) { ret = -errno; goto exit; } ret = ioctl (fd, I2C_SMBUS, &payload); if (ret < 0) { ret = -errno; goto exit; } exit: free(buf); return ret; } int i2c_write (int fd, uint8_t dev, uint8_t regaddr, uint16_t *data) { unsigned long funcs; if (ioctl(fd, I2C_FUNCS, &funcs) < 0) { return -errno; } if (funcs & I2C_FUNC_I2C) { return i2c_ioctl_write (fd, dev, regaddr, data); } else if (funcs & I2C_FUNC_SMBUS_WORD_DATA) { return i2c_ioctl_smbus_write (fd, dev, regaddr, data); } else { return -ENOSYS; } } int parse_args (uint8_t *regaddr, uint16_t *data, char *argv[]) { char *endptr; int i; *regaddr = (uint8_t) strtol(argv[1], &endptr, 0); if (errno || endptr == argv[1]) { return -1; } for (i = 0; i < sizeof(data) / sizeof(data[0]); i ++) { data[i] = (uint16_t) strtol(argv[i + 2], &endptr, 0); if (errno || endptr == argv[i + 2]) { return -1; } } return 0; } void usage (int argc, char *argv[]) { fprintf(stderr, "Usage: %s regaddr data [data]*\n", argv[0]); fprintf(stderr, " regaddr The 8-bit register address to write to.\n"); fprintf(stderr, " data The 16-bit data to be written.\n"); exit(-1); } int main (int argc, char *argv[]) { uint8_t regaddr; uint16_t *data; int fd; int ret = 0; if (argc < 3) { usage(argc, argv); } data = malloc((argc - 2) * sizeof(uint16_t)); if (data == NULL) { fprintf (stderr, "%s.\n", strerror(ENOMEM)); return -ENOMEM; } if (parse_args(®addr, data, argv) != 0) { free(data); usage(argc, argv); } fd = open(I2C_ADAPTER, O_RDWR | O_NONBLOCK); ret = i2c_write(fd, I2C_DEVICE, regaddr, data); close(fd); if (ret) { fprintf (stderr, "%s.\n", strerror(-ret)); } free(data); return ret; }
I'm not too sure if this helps because I don't use ioctl I2C_RDWR but I've been using the following code with success:
int fd; fd = open("/dev/i2c-5", O_RDWR); ioctl(fd, I2C_SLAVE_FORCE, 0x20); i2c_smbus_write_word_data(fd, ___, ___); i2c_smbus_read_word_data(fd, ___);
All I do is set I2C_SLAVE_FORCE once at the beginning and I can read and write as much as I want to after that.
PS - This is just a code sample and obviously you should check the returns of all of these functions. I'm using this code to communicate with a digital I/O chip. The two i2c_* functions are just wrappers that call ioctl(fd, I2C_SMBUS, &args); where args is a struct i2c_smbus_ioctl_data type.
If you use the read()
and write()
methods, calling ioctl
with I2C_SLAVE
once is enough. You can also use I2C_SLAVE_FORCE
if the device is already in use.
However I haven't yet found a consistent way to read specific registers for every device using the read()/write()
methods.