svn commit: r354973 - head/sys/dev/iicbus

Ian Lepore ian at FreeBSD.org
Thu Nov 21 19:13:06 UTC 2019


Author: ian
Date: Thu Nov 21 19:13:05 2019
New Revision: 354973
URL: https://svnweb.freebsd.org/changeset/base/354973

Log:
  Rewrite iicdev_writeto() to use a single buffer and a single iic_msg, rather
  than effectively doing scatter/gather IO with a pair of iic_msgs that direct
  the controller to do a single transfer with no bus STOP/START between the
  two buffers.  It turns out we have multiple i2c hardware drivers that don't
  honor the NOSTOP and NOSTART flags; sometimes they just try to do the
  transfers anyway, creating confusing failures or leading to corrupted data.

Modified:
  head/sys/dev/iicbus/iiconf.c

Modified: head/sys/dev/iicbus/iiconf.c
==============================================================================
--- head/sys/dev/iicbus/iiconf.c	Thu Nov 21 18:49:54 2019	(r354972)
+++ head/sys/dev/iicbus/iiconf.c	Thu Nov 21 19:13:05 2019	(r354973)
@@ -540,25 +540,47 @@ iicdev_readfrom(device_t slavedev, uint8_t regaddr, vo
 int iicdev_writeto(device_t slavedev, uint8_t regaddr, void *buffer,
     uint16_t buflen, int waithow)
 {
-	struct iic_msg msgs[2];
-	uint8_t slaveaddr;
+	struct iic_msg msg;
+	uint8_t local_buffer[32];
+	uint8_t *bufptr;
+	size_t bufsize;
+	int error;
 
 	/*
-	 * Two transfers back to back with no stop or start between them; first
-	 * we write the address then we write the data to that address, all in a
-	 * single transfer from two scattered buffers.
+	 * Ideally, we would do two transfers back to back with no stop or start
+	 * between them using an array of 2 iic_msgs; first we'd write the
+	 * address byte using the IIC_M_NOSTOP flag, then we write the data
+	 * using IIC_M_NOSTART, all in a single transfer.  Unfortunately,
+	 * several i2c hardware drivers don't support that (perhaps because the
+	 * hardware itself can't support it).  So instead we gather the
+	 * scattered bytes into a single buffer here before writing them using a
+	 * single iic_msg.  This function is typically used to write a few bytes
+	 * at a time, so we try to use a small local buffer on the stack, but
+	 * fall back to allocating a temporary buffer when necessary.
 	 */
-	slaveaddr = iicbus_get_addr(slavedev);
 
-	msgs[0].slave = slaveaddr;
-	msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
-	msgs[0].len   = 1;
-	msgs[0].buf   = ®addr;
+	bufsize = buflen + 1;
+	if (bufsize <= sizeof(local_buffer)) {
+		bufptr = local_buffer;
+	} else {
+		bufptr = malloc(bufsize, M_DEVBUF,
+		    (waithow & IIC_WAIT) ? M_WAITOK : M_NOWAIT);
+		if (bufptr == NULL)
+			return (errno2iic(ENOMEM));
+	}
 
-	msgs[1].slave = slaveaddr;
-	msgs[1].flags = IIC_M_WR | IIC_M_NOSTART;
-	msgs[1].len   = buflen;
-	msgs[1].buf   = buffer;
+	bufptr[0] = regaddr;
+	memcpy(&bufptr[1], buffer, buflen);
 
-	return (iicbus_transfer_excl(slavedev, msgs, nitems(msgs), waithow));
+	msg.slave = iicbus_get_addr(slavedev);
+	msg.flags = IIC_M_WR;
+	msg.len   = bufsize;
+	msg.buf   = bufptr;
+
+	error = iicbus_transfer_excl(slavedev, &msg, 1, waithow);
+
+	if (bufptr != local_buffer)
+		free(bufptr, M_DEVBUF);
+
+	return (error);
 }


More information about the svn-src-all mailing list