Module Name:    src
Committed By:   tnn
Date:           Tue Nov  5 19:59:35 UTC 2019

Modified Files:
        src/sys/dev/i2c: ssdfb_i2c.c

Log Message:
ssdfb: fix i2c transfer error with some controllers

If the controller doesn't support the full 128 byte transfer size we need,
then split the write across multiple transactions.


To generate a diff of this commit:
cvs rdiff -u -r1.4 -r1.5 src/sys/dev/i2c/ssdfb_i2c.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/i2c/ssdfb_i2c.c
diff -u src/sys/dev/i2c/ssdfb_i2c.c:1.4 src/sys/dev/i2c/ssdfb_i2c.c:1.5
--- src/sys/dev/i2c/ssdfb_i2c.c:1.4	Sat Nov  2 14:23:59 2019
+++ src/sys/dev/i2c/ssdfb_i2c.c	Tue Nov  5 19:59:35 2019
@@ -1,4 +1,4 @@
-/* $NetBSD: ssdfb_i2c.c,v 1.4 2019/11/02 14:23:59 tnn Exp $ */
+/* $NetBSD: ssdfb_i2c.c,v 1.5 2019/11/05 19:59:35 tnn Exp $ */
 
 /*
  * Copyright (c) 2019 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ssdfb_i2c.c,v 1.4 2019/11/02 14:23:59 tnn Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ssdfb_i2c.c,v 1.5 2019/11/05 19:59:35 tnn Exp $");
 
 #include <sys/param.h>
 #include <sys/device.h>
@@ -43,12 +43,16 @@ struct ssdfb_i2c_softc {
 	struct		ssdfb_softc sc;
 	i2c_tag_t	sc_i2c_tag;
 	i2c_addr_t	sc_i2c_addr;
+	size_t		sc_transfer_size;
 };
 
 static int	ssdfb_i2c_match(device_t, cfdata_t, void *);
 static void	ssdfb_i2c_attach(device_t, device_t, void *);
 static int	ssdfb_i2c_detach(device_t, int);
 
+static int	ssdfb_i2c_probe_transfer_size(struct ssdfb_i2c_softc *, bool);
+static int	ssdfb_i2c_transfer(struct ssdfb_i2c_softc *, uint8_t, uint8_t *,
+				size_t, int);
 static int	ssdfb_i2c_cmd(void *, uint8_t *, size_t, bool);
 static int	ssdfb_i2c_transfer_rect(void *, uint8_t, uint8_t, uint8_t,
 				uint8_t, uint8_t *, size_t, bool);
@@ -132,6 +136,61 @@ ssdfb_i2c_detach(device_t self, int flag
 }
 
 static int
+ssdfb_i2c_probe_transfer_size(struct ssdfb_i2c_softc *sc, bool usepoll)
+{
+	int flags = usepoll ? I2C_F_POLL : 0;
+	uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK;
+	int error;
+	uint8_t buf[128];
+	size_t len;
+
+	error = iic_acquire_bus(sc->sc_i2c_tag, flags);
+	if (error)
+		return error;
+	len = sizeof(buf);
+	memset(buf, 0, len);
+	while (len > 0) {
+		error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
+		    sc->sc_i2c_addr, &cb, sizeof(cb), buf, len, flags);
+		if (!error) {
+			break;
+		}
+		len >>= 1;
+	}
+	if (!error && len < 2) {
+		error = E2BIG;
+	} else {
+		sc->sc_transfer_size = len;
+	}
+	(void) iic_release_bus(sc->sc_i2c_tag, flags);
+
+	return error;
+}
+
+static int
+ssdfb_i2c_transfer(struct ssdfb_i2c_softc *sc, uint8_t cb, uint8_t *data,
+		   size_t len, int flags)
+{
+	int error;
+	size_t xfer_size = sc->sc_transfer_size;
+
+	while (len >= xfer_size) {
+		error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
+		    sc->sc_i2c_addr, &cb, sizeof(cb), data, xfer_size, flags);
+		if (error)
+			return error;
+		len -= xfer_size;
+		data += xfer_size;
+	}
+	if (len > 0) {
+		error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
+		    sc->sc_i2c_addr, &cb, sizeof(cb), data, len, flags);
+	}
+
+	return error;
+}
+
+static int
 ssdfb_i2c_cmd(void *cookie, uint8_t *cmd, size_t len, bool usepoll)
 {
 	struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie;
@@ -154,9 +213,6 @@ ssdfb_i2c_transfer_rect(void *cookie, ui
     uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll)
 {
 	struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie;
-	int flags = usepoll ? I2C_F_POLL : 0;
-	uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK;
-	uint8_t data[] = {0, 0, 0};
 	uint8_t cmd[2];
 	int error;
 
@@ -177,13 +233,12 @@ ssdfb_i2c_transfer_rect(void *cookie, ui
 	}
 
 	if (sc->sc.sc_transfer_rect != ssdfb_smbus_transfer_rect) {
-		error = iic_acquire_bus(sc->sc_i2c_tag, flags);
+		error = ssdfb_i2c_probe_transfer_size(sc, usepoll);
 		if (error)
 			return error;
-		error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
-		    sc->sc_i2c_addr, &cb, sizeof(cb), data, sizeof(data), flags);
-		(void) iic_release_bus(sc->sc_i2c_tag, flags);
-		if (error) {
+		aprint_verbose_dev(sc->sc.sc_dev, "%zd-byte transfers\n",
+		    sc->sc_transfer_size);
+		if (sc->sc_transfer_size == 2) {
 			sc->sc.sc_transfer_rect = ssdfb_smbus_transfer_rect;
 		}
 	}
@@ -238,8 +293,7 @@ ssdfb_i2c_transfer_rect_ssd1306(void *co
 	if (error)
 		goto out;
 	while (frompage <= topage) {
-		error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
-		    sc->sc_i2c_addr, &cb, sizeof(cb), p, len, flags);
+		error = ssdfb_i2c_transfer(sc, cb, p, len, flags);
 		if (error)
 			goto out;
 		frompage++;
@@ -278,8 +332,7 @@ ssdfb_i2c_transfer_rect_sh1106(void *coo
 		    sc->sc_i2c_addr, &cc, sizeof(cc), cmds, sizeof(cmds), flags);
 		if (error)
 			goto out;
-		error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
-		    sc->sc_i2c_addr, &cb, sizeof(cb), p, len, flags);
+		error = ssdfb_i2c_transfer(sc, cb, p, len, flags);
 		if (error)
 			goto out;
 		frompage++;
@@ -364,5 +417,6 @@ ssdfb_smbus_transfer_rect(void *cookie, 
 	}
 out:
 	(void) iic_release_bus(sc->sc_i2c_tag, flags);
+
 	return error;
 }

Reply via email to