Add a new 'tr' (transfer) mode to i2c(8) to support more i2c controllers.

Some i2c controller hardware does not provide a way to do individual START,
REPEAT-START and STOP actions on the i2c bus.  Instead, they can only do
a complete transfer as a single operation.  Typically they can do either
START-data-STOP or START-data-REPEATSTART-data-STOP.  In the i2c driver
framework, this corresponds to the iicbus_transfer method.  In the userland
interface they are initiated with the I2CRDWR ioctl command.

These changes add a new 'tr' mode which can be specified with the '-m'
command line option.  This mode should work on all hardware; when an i2c
controller driver doesn't directly support the iicbus_transfer method,
code in the i2c driver framework uses the lower-level START/REPEAT/STOP
methods to implement the transfer.  After this new mode has gotten some
testing on various hardware, the 'tr' mode should probably become the
new default mode.

PR:		189914
This commit is contained in:
Ian Lepore 2019-05-22 21:06:10 +00:00
parent 83d0c3846d
commit a30555576c
2 changed files with 117 additions and 30 deletions

View File

@ -25,7 +25,7 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd January 23, 2009 .Dd May 22, 2019
.Dt I2C 8 .Dt I2C 8
.Os .Os
.Sh NAME .Sh NAME
@ -39,7 +39,7 @@
.Op Fl w Ar 0|8|16 .Op Fl w Ar 0|8|16
.Op Fl o Ar offset .Op Fl o Ar offset
.Op Fl c Ar count .Op Fl c Ar count
.Op Fl m Ar ss|rs|no .Op Fl m Ar tr|ss|rs|no
.Op Fl b .Op Fl b
.Op Fl v .Op Fl v
.Nm .Nm
@ -72,10 +72,29 @@ number of bytes to transfer (dec).
transfer direction: r - read, w - write. transfer direction: r - read, w - write.
.It Fl f Ar device .It Fl f Ar device
I2C bus to use (default is /dev/iic0). I2C bus to use (default is /dev/iic0).
.It Fl m Ar ss|rs|no .It Fl m Ar tr|ss|rs|no
addressing mode, i.e., I2C bus operations performed after the offset for the addressing mode, i.e., I2C bus operations performed after the offset for the
transfer has been written to the device and before the actual read/write transfer has been written to the device and before the actual read/write
operation. rs - repeated start; ss - stop start; no - none. operation.
.Bl -tag -compact -offset indent
.It Va tr
complete-transfer
.It Va ss
stop then start
.It Va rs
repeated start
.It Va no
none
.El
Some I2C bus hardware does not provide control over the individual start,
repeat-start, and stop operations.
Such hardware can only perform a complete transfer of the offset and the
data as a single operation.
The
.Va tr
mode creates control structures describing the transfer and submits them
to the driver as a single complete transaction.
This mode works on all types of I2C hardware.
.It Fl n Ar skip_addr .It Fl n Ar skip_addr
skip address - address(es) to be skipped during bus scan. skip address - address(es) to be skipped during bus scan.
There are two ways to specify addresses to ignore: by range 'a..b' or There are two ways to specify addresses to ignore: by range 'a..b' or

View File

@ -47,6 +47,7 @@ __FBSDID("$FreeBSD$");
#define I2C_MODE_NONE 1 #define I2C_MODE_NONE 1
#define I2C_MODE_STOP_START 2 #define I2C_MODE_STOP_START 2
#define I2C_MODE_REPEATED_START 3 #define I2C_MODE_REPEATED_START 3
#define I2C_MODE_TRANSFER 4
struct options { struct options {
int width; int width;
@ -73,7 +74,7 @@ usage(void)
{ {
fprintf(stderr, "usage: %s -a addr [-f device] [-d [r|w]] [-o offset] " fprintf(stderr, "usage: %s -a addr [-f device] [-d [r|w]] [-o offset] "
"[-w [0|8|16]] [-c count] [-m [ss|rs|no]] [-b] [-v]\n", "[-w [0|8|16]] [-c count] [-m [tr|ss|rs|no]] [-b] [-v]\n",
getprogname()); getprogname());
fprintf(stderr, " %s -s [-f device] [-n skip_addr] -v\n", fprintf(stderr, " %s -s [-f device] [-n skip_addr] -v\n",
getprogname()); getprogname());
@ -297,24 +298,9 @@ static int
i2c_write(char *dev, struct options i2c_opt, char *i2c_buf) i2c_write(char *dev, struct options i2c_opt, char *i2c_buf)
{ {
struct iiccmd cmd; struct iiccmd cmd;
int ch, i, error, fd, bufsize; int error, fd, bufsize;
char *err_msg, *buf; char *err_msg, *buf;
/*
* Read data to be written to the chip from stdin
*/
if (i2c_opt.verbose && !i2c_opt.binary)
fprintf(stderr, "Enter %u bytes of data: ", i2c_opt.count);
for (i = 0; i < i2c_opt.count; i++) {
ch = getchar();
if (ch == EOF) {
free(i2c_buf);
err(1, "not enough data, exiting\n");
}
i2c_buf[i] = ch;
}
fd = open(dev, O_RDWR); fd = open(dev, O_RDWR);
if (fd == -1) { if (fd == -1) {
free(i2c_buf); free(i2c_buf);
@ -558,6 +544,72 @@ err2:
return (1); return (1);
} }
/*
* i2c_rdwr_transfer() - use I2CRDWR to conduct a complete i2c transfer.
*
* Some i2c hardware is unable to provide direct control over START, REPEAT-
* START, and STOP operations. Such hardware can only perform a complete
* START-<data>-STOP or START-<data>-REPEAT-START-<data>-STOP sequence as a
* single operation. The driver framework refers to this sequence as a
* "transfer" so we call it "transfer mode". We assemble either one or two
* iic_msg structures to describe the IO operations, and hand them off to the
* driver to be handled as a single transfer.
*/
static int
i2c_rdwr_transfer(char *dev, struct options i2c_opt, char *i2c_buf)
{
struct iic_msg msgs[2];
struct iic_rdwr_data xfer;
int fd, i;
union {
uint8_t buf[2];
uint8_t off8;
uint16_t off16;
} off;
i = 0;
if (i2c_opt.width > 0) {
msgs[i].flags = IIC_M_WR | IIC_M_NOSTOP;
msgs[i].slave = i2c_opt.addr;
msgs[i].buf = off.buf;
if (i2c_opt.width == 8) {
off.off8 = (uint8_t)i2c_opt.off;
msgs[i].len = 1;
} else {
off.off16 = (uint16_t)i2c_opt.off;
msgs[i].len = 2;
}
++i;
}
/*
* If the transfer direction is write and we did a write of the offset
* above, then we need to elide the start; this transfer is just more
* writing that follows the one started above. For a read, we always do
* a start; if we did an offset write above it'll be a repeat-start
* because of the NOSTOP flag used above.
*/
if (i2c_opt.dir == 'w')
msgs[i].flags = IIC_M_WR | (i > 0) ? IIC_M_NOSTART : 0;
else
msgs[i].flags = IIC_M_RD;
msgs[i].slave = i2c_opt.addr;
msgs[i].len = i2c_opt.count;
msgs[i].buf = i2c_buf;
++i;
xfer.msgs = msgs;
xfer.nmsgs = i;
if ((fd = open(dev, O_RDWR)) == -1)
err(1, "open(%s) failed", dev);
if (ioctl(fd, I2CRDWR, &xfer) == -1 )
err(1, "ioctl(I2CRDWR) failed");
close(fd);
return (0);
}
int int
main(int argc, char** argv) main(int argc, char** argv)
{ {
@ -620,6 +672,8 @@ main(int argc, char** argv)
i2c_opt.mode = I2C_MODE_STOP_START; i2c_opt.mode = I2C_MODE_STOP_START;
else if (!strcmp(optarg, "rs")) else if (!strcmp(optarg, "rs"))
i2c_opt.mode = I2C_MODE_REPEATED_START; i2c_opt.mode = I2C_MODE_REPEATED_START;
else if (!strcmp(optarg, "tr"))
i2c_opt.mode = I2C_MODE_TRANSFER;
else else
usage(); usage();
break; break;
@ -687,19 +741,33 @@ main(int argc, char** argv)
if (i2c_buf == NULL) if (i2c_buf == NULL)
err(1, "data malloc"); err(1, "data malloc");
/*
* For a write, read the data to be written to the chip from stdin.
*/
if (i2c_opt.dir == 'w') { if (i2c_opt.dir == 'w') {
error = i2c_write(dev, i2c_opt, i2c_buf); if (i2c_opt.verbose && !i2c_opt.binary)
if (error) { fprintf(stderr, "Enter %u bytes of data: ",
free(i2c_buf); i2c_opt.count);
return (1); for (i = 0; i < i2c_opt.count; i++) {
ch = getchar();
if (ch == EOF) {
free(i2c_buf);
err(1, "not enough data, exiting\n");
}
i2c_buf[i] = ch;
} }
} }
if (i2c_opt.dir == 'r') {
if (i2c_opt.mode == I2C_MODE_TRANSFER)
error = i2c_rdwr_transfer(dev, i2c_opt, i2c_buf);
else if (i2c_opt.dir == 'w')
error = i2c_write(dev, i2c_opt, i2c_buf);
else
error = i2c_read(dev, i2c_opt, i2c_buf); error = i2c_read(dev, i2c_opt, i2c_buf);
if (error) {
free(i2c_buf); if (error != 0) {
return (1); free(i2c_buf);
} return (1);
} }
if (i2c_opt.verbose) if (i2c_opt.verbose)