io/spi: add CSChange config option

Some SPI devices use a half-duplex request/response model where the user
submits a request and then continues reading until a full response is
received. This requires multiple Tx calls, but also requires that the
device's chipselect stays enabled until the full response has been read.

The linux spidev interface has a flag cs_change which, when set to true,
causes the device's chipselect to stay enabled after the data has been
written. The SPI devfs driver payload contains a field for this option,
but it is not currently used.

This change exposes that parameter by adding a CSChange option to the
SPI driver's configuration options and a SetCSChange function to the
spi.Device. The following flow then becomes possible:

* call SetCSChange(true) so the chipselect stays enabled after Tx calls
* call Tx() to write the request data
* call Tx() repeatedly to continue reading until full response data has
  been received
* call SetCSChange(false) so the chipselect gets disabled after Tx calls
* call Tx() one more time to release the device

Fixes #16239

Change-Id: Ide41f706a2605d483e160f861082d8f7e1796e82
Reviewed-on: https://go-review.googlesource.com/24910
Reviewed-by: Jaana Burcu Dogan <jbd@google.com>
diff --git a/io/spi/devfs.go b/io/spi/devfs.go
index a24c067..04b54c3 100644
--- a/io/spi/devfs.go
+++ b/io/spi/devfs.go
@@ -85,11 +85,12 @@
 }
 
 type devfsConn struct {
-	f     *os.File
-	mode  uint8
-	speed uint32
-	bits  uint8
-	delay uint16
+	f        *os.File
+	mode     uint8
+	speed    uint32
+	bits     uint8
+	delay    uint16
+	csChange uint8
 }
 
 func (c *devfsConn) Configure(k, v int) error {
@@ -119,6 +120,8 @@
 		}
 	case driver.Delay:
 		c.delay = uint16(v)
+	case driver.CSChange:
+		c.csChange = uint8(v)
 	default:
 		return fmt.Errorf("unknown key: %v", k)
 	}
@@ -132,12 +135,13 @@
 	// TODO(jbd): len(w) == len(r)?
 	// TODO(jbd): Allow nil w.
 	p := payload{
-		tx:     uint64(uintptr(unsafe.Pointer(&w[0]))),
-		rx:     uint64(uintptr(unsafe.Pointer(&r[0]))),
-		length: uint32(len(w)),
-		speed:  c.speed,
-		delay:  c.delay,
-		bits:   c.bits,
+		tx:       uint64(uintptr(unsafe.Pointer(&w[0]))),
+		rx:       uint64(uintptr(unsafe.Pointer(&r[0]))),
+		length:   uint32(len(w)),
+		speed:    c.speed,
+		delay:    c.delay,
+		bits:     c.bits,
+		csChange: c.csChange,
 	}
 	// TODO(jbd): Read from the device and fill rx.
 	return c.ioctl(msgRequestCode(1), uintptr(unsafe.Pointer(&p)))
diff --git a/io/spi/driver/driver.go b/io/spi/driver/driver.go
index 0f28201..7634440 100644
--- a/io/spi/driver/driver.go
+++ b/io/spi/driver/driver.go
@@ -11,6 +11,7 @@
 	MaxSpeed
 	Order
 	Delay
+	CSChange
 )
 
 // Opener is an interface to be implemented by the SPI driver to open
@@ -34,6 +35,7 @@
 	//    Some SPI devices require a minimum amount of wait time after
 	//    each frame write. If set, Delay amount of usecs are inserted after
 	//    each write.
+	//  - CSChange, whether to leave the device's chipselect active after a Tx.
 	//
 	// SPI devices can override these values.
 	Configure(k, v int) error
diff --git a/io/spi/spi.go b/io/spi/spi.go
index 999f429..e351a57 100644
--- a/io/spi/spi.go
+++ b/io/spi/spi.go
@@ -67,6 +67,15 @@
 	return d.conn.Configure(driver.Delay, int(t.Nanoseconds()/1000))
 }
 
+// SetCSChange sets whether to leave the chipselect enabled after a Tx.
+func (d *Device) SetCSChange(leaveEnabled bool) error {
+	v := 0
+	if leaveEnabled {
+		v = 1
+	}
+	return d.conn.Configure(driver.CSChange, v)
+}
+
 // Tx performs a duplex transmission to write w to the SPI device
 // and read len(r) bytes to r.
 // User should not mutate the w and r until this call returns.