Skip site navigation (1)Skip section navigation (2)
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>