io/i2c: add support 10-bit addresses

10-bit addresses can range from 0 to (1111111111)2. That's why we need
to know in advance if the user is working with a 10-bit addressed
device.

The kernel driver also provides an ioctl function to enable the 10-bit
mode before setting the address globally on the device. Devfs enables
10-bit mode if it is required.

Change-Id: I71e64ab77c68903bbe9c0ac589300eb1bc29ff43
Reviewed-on: https://go-review.googlesource.com/22516
Reviewed-by: Minux Ma <minux@golang.org>
diff --git a/io/i2c/devfs.go b/io/i2c/devfs.go
index 00853a7..34d9c0a 100644
--- a/io/i2c/devfs.go
+++ b/io/i2c/devfs.go
@@ -17,17 +17,24 @@
 type Devfs struct{}
 
 const (
-	i2c_SLAVE = 0x0703 // TODO(jbd): Allow users to use I2C_SLAVE_FORCE?
+	i2c_SLAVE  = 0x0703 // TODO(jbd): Allow users to use I2C_SLAVE_FORCE?
+	i2c_TENBIT = 0x0704
 )
 
 // TODO(jbd): Support I2C_RETRIES and I2C_TIMEOUT at the driver and implementation level.
 
-func (d *Devfs) Open(bus, addr int) (driver.Conn, error) {
+func (d *Devfs) Open(bus, addr int, tenbit bool) (driver.Conn, error) {
 	f, err := os.OpenFile(fmt.Sprintf("/dev/i2c-%d", bus), os.O_RDWR, os.ModeDevice)
 	if err != nil {
 		return nil, err
 	}
 	conn := &devfsConn{f: f}
+	if tenbit {
+		if err := conn.ioctl(i2c_TENBIT, uintptr(1)); err != nil {
+			conn.Close()
+			return nil, fmt.Errorf("cannot enable the 10-bit address mode on bus %v: %v", bus, err)
+		}
+	}
 	if err := conn.ioctl(i2c_SLAVE, uintptr(addr)); err != nil {
 		conn.Close()
 		return nil, fmt.Errorf("error opening the address (%v) on the bus (%v): %v", addr, bus, err)
diff --git a/io/i2c/driver/driver.go b/io/i2c/driver/driver.go
index a2be469..09f3bd3 100644
--- a/io/i2c/driver/driver.go
+++ b/io/i2c/driver/driver.go
@@ -7,9 +7,10 @@
 
 // Opener is an interface to be implemented by the I2C driver to open
 // a connection to an I2C device with the specified bus number and I2C address.
-// Open should support 7-bit and 10-bit I2C addresses.
+// Open should support 7-bit addresses and should provide support for
+// 10-bit I2C addresses if tenbit is true.
 type Opener interface {
-	Open(bus, addr int) (Conn, error)
+	Open(bus, addr int, tenbit bool) (Conn, error)
 }
 
 // Conn represents an active connection to an I2C device.
diff --git a/io/i2c/i2c.go b/io/i2c/i2c.go
index f48a213..f8f0cf1 100644
--- a/io/i2c/i2c.go
+++ b/io/i2c/i2c.go
@@ -11,15 +11,21 @@
 	"golang.org/x/exp/io/i2c/driver"
 )
 
+const tenbitMask = 1 << 12
+
 // Device represents an I2C device. Devices must be closed once
 // they are no longer in use.
 type Device struct {
 	conn driver.Conn
 }
 
+// TenBit marks an I2C address as a 10-bit address.
+func TenBit(addr int) int {
+	return addr | tenbitMask
+}
+
 // TOOD(jbd): Do we need higher level I2C packet writers and readers?
 // TODO(jbd): Support bidirectional communication.
-// TODO(jbd): How do we support 10-bit addresses and how to enable 10-bit on devfs?
 
 // Read reads len(buf) bytes from the device.
 func (d *Device) Read(buf []byte) error {
@@ -47,13 +53,19 @@
 }
 
 // Open opens an I2C device with the given I2C address on the specified bus.
+// Use TenBit to mark your address if your device works with 10-bit addresses.
 func Open(o driver.Opener, bus, addr int) (*Device, error) {
 	if o == nil {
 		o = &Devfs{}
 	}
-	conn, err := o.Open(bus, addr)
+	addr, tenbit := resolveAddr(addr)
+	conn, err := o.Open(bus, addr, tenbit)
 	if err != nil {
 		return nil, err
 	}
 	return &Device{conn: conn}, nil
 }
+
+func resolveAddr(a int) (addr int, tenbit bool) {
+	return a & (tenbitMask - 1), a&tenbitMask == tenbitMask
+}
diff --git a/io/i2c/i2c_test.go b/io/i2c/i2c_test.go
new file mode 100644
index 0000000..a48baad
--- /dev/null
+++ b/io/i2c/i2c_test.go
@@ -0,0 +1,31 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package i2c
+
+import (
+	"testing"
+)
+
+func TestTenBit(t *testing.T) {
+	tc := []struct {
+		masked int
+		addr   int
+		tenbit bool
+	}{
+		{TenBit(0x5), 0x5, true},
+		{0x5, 0x5, false},
+		{TenBit(0x40), 0x40, true},
+	}
+
+	for _, tt := range tc {
+		unmasked, tenbit := resolveAddr(tt.masked)
+		if want, got := tt.tenbit, tenbit; got != want {
+			t.Errorf("want address %b as 10-bit; got non 10-bit", tt.addr)
+		}
+		if want, got := tt.addr, unmasked; got != want {
+			t.Errorf("want address %b; got %b", want, got)
+		}
+	}
+}