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)
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to