Date: Sun, 23 Jun 2019 15:55:42 +0000 (UTC) From: Ian Lepore <ian@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-12@freebsd.org Subject: svn commit: r349310 - stable/12/usr.sbin/i2c Message-ID: <201906231555.x5NFtgdc055604@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: ian Date: Sun Jun 23 15:55:41 2019 New Revision: 349310 URL: https://svnweb.freebsd.org/changeset/base/349310 Log: MFC r348120: 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 Modified: stable/12/usr.sbin/i2c/i2c.8 stable/12/usr.sbin/i2c/i2c.c Directory Properties: stable/12/ (props changed) Modified: stable/12/usr.sbin/i2c/i2c.8 ============================================================================== --- stable/12/usr.sbin/i2c/i2c.8 Sun Jun 23 15:09:39 2019 (r349309) +++ stable/12/usr.sbin/i2c/i2c.8 Sun Jun 23 15:55:41 2019 (r349310) @@ -25,7 +25,7 @@ .\" .\" $FreeBSD$ .\" -.Dd January 23, 2009 +.Dd May 22, 2019 .Dt I2C 8 .Os .Sh NAME @@ -39,7 +39,7 @@ .Op Fl w Ar 0|8|16 .Op Fl o Ar offset .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 v .Nm @@ -72,10 +72,29 @@ number of bytes to transfer (dec). transfer direction: r - read, w - write. .It Fl f Ar device 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 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 skip address - address(es) to be skipped during bus scan. There are two ways to specify addresses to ignore: by range 'a..b' or Modified: stable/12/usr.sbin/i2c/i2c.c ============================================================================== --- stable/12/usr.sbin/i2c/i2c.c Sun Jun 23 15:09:39 2019 (r349309) +++ stable/12/usr.sbin/i2c/i2c.c Sun Jun 23 15:55:41 2019 (r349310) @@ -47,6 +47,7 @@ __FBSDID("$FreeBSD$"); #define I2C_MODE_NONE 1 #define I2C_MODE_STOP_START 2 #define I2C_MODE_REPEATED_START 3 +#define I2C_MODE_TRANSFER 4 struct options { int width; @@ -73,7 +74,7 @@ usage(void) { 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()); fprintf(stderr, " %s -s [-f device] [-n skip_addr] -v\n", getprogname()); @@ -297,24 +298,9 @@ static int i2c_write(char *dev, struct options i2c_opt, char *i2c_buf) { struct iiccmd cmd; - int ch, i, error, fd, bufsize; + int error, fd, bufsize; 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); if (fd == -1) { free(i2c_buf); @@ -558,6 +544,72 @@ err2: 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 main(int argc, char** argv) { @@ -620,6 +672,8 @@ main(int argc, char** argv) i2c_opt.mode = I2C_MODE_STOP_START; else if (!strcmp(optarg, "rs")) i2c_opt.mode = I2C_MODE_REPEATED_START; + else if (!strcmp(optarg, "tr")) + i2c_opt.mode = I2C_MODE_TRANSFER; else usage(); break; @@ -687,19 +741,33 @@ main(int argc, char** argv) if (i2c_buf == NULL) err(1, "data malloc"); + /* + * For a write, read the data to be written to the chip from stdin. + */ if (i2c_opt.dir == 'w') { - error = i2c_write(dev, i2c_opt, i2c_buf); - if (error) { - free(i2c_buf); - return (1); + 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; } } - 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); - if (error) { - free(i2c_buf); - return (1); - } + + if (error != 0) { + free(i2c_buf); + return (1); } if (i2c_opt.verbose)
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201906231555.x5NFtgdc055604>