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:
parent
83d0c3846d
commit
a30555576c
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user