gobot.io/x/gobot/v2@v2.1.0/system/i2c_device.go (about)

     1  package system
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"sync"
     8  	"syscall"
     9  	"unsafe"
    10  )
    11  
    12  const (
    13  	i2cDeviceDebug  = false
    14  	forceSetAddress = false // normally address will be written only when changed, this behavior can be overridden
    15  )
    16  
    17  const (
    18  	// From  /usr/include/linux/i2c-dev.h:
    19  	// ioctl signals
    20  	I2C_SLAVE = 0x0703
    21  	I2C_FUNCS = 0x0705
    22  	I2C_SMBUS = 0x0720
    23  	// Read/write markers
    24  	I2C_SMBUS_READ  = 1
    25  	I2C_SMBUS_WRITE = 0
    26  
    27  	// From  /usr/include/linux/i2c.h:
    28  	// Adapter functionality
    29  	I2C_FUNC_SMBUS_READ_BYTE        = 0x00020000
    30  	I2C_FUNC_SMBUS_WRITE_BYTE       = 0x00040000
    31  	I2C_FUNC_SMBUS_READ_BYTE_DATA   = 0x00080000
    32  	I2C_FUNC_SMBUS_WRITE_BYTE_DATA  = 0x00100000
    33  	I2C_FUNC_SMBUS_READ_WORD_DATA   = 0x00200000
    34  	I2C_FUNC_SMBUS_WRITE_WORD_DATA  = 0x00400000
    35  	I2C_FUNC_SMBUS_READ_BLOCK_DATA  = 0x01000000
    36  	I2C_FUNC_SMBUS_WRITE_BLOCK_DATA = 0x02000000
    37  	I2C_FUNC_SMBUS_READ_I2C_BLOCK   = 0x04000000 // I2C-like block transfer with 1-byte reg. addr.
    38  	I2C_FUNC_SMBUS_WRITE_I2C_BLOCK  = 0x08000000 // I2C-like block transfer with 1-byte reg. addr.
    39  	// Transaction types
    40  	I2C_SMBUS_BYTE             = 1
    41  	I2C_SMBUS_BYTE_DATA        = 2
    42  	I2C_SMBUS_WORD_DATA        = 3
    43  	I2C_SMBUS_PROC_CALL        = 4
    44  	I2C_SMBUS_BLOCK_DATA       = 5
    45  	I2C_SMBUS_I2C_BLOCK_BROKEN = 6
    46  	I2C_SMBUS_BLOCK_PROC_CALL  = 7 /* SMBus 2.0 */
    47  	I2C_SMBUS_I2C_BLOCK_DATA   = 8 /* SMBus 2.0 */
    48  )
    49  
    50  type i2cSmbusIoctlData struct {
    51  	readWrite byte
    52  	command   byte
    53  	protocol  uint32
    54  	data      unsafe.Pointer
    55  }
    56  
    57  type i2cDevice struct {
    58  	location    string
    59  	sys         systemCaller
    60  	fs          filesystem
    61  	file        File
    62  	funcs       uint64 // adapter functionality mask
    63  	lastAddress int
    64  	mutex       sync.Mutex
    65  }
    66  
    67  // NewI2cDevice returns a Linux Kernel access by ioctrl to the given i2c bus location (character device).
    68  // Important note for "os.ModeExclusive": this is undefined without create the file for character devices, this means
    69  // a second open will not return an error e.g. due to a busy resource. If this is not wanted, e.g. to minimize count of
    70  // open fd's this needs to be prevented at caller side by implementing a caching mechanism. Furthermore this behavior
    71  // can lead to problems with multiple devices on the same bus because the cycle SetAddress()...Read()/Write() etc. can
    72  // be interrupted when using multiple instances for the same location.
    73  func (a *Accesser) NewI2cDevice(location string) (*i2cDevice, error) {
    74  	if location == "" {
    75  		return nil, fmt.Errorf("the given character device location is empty")
    76  	}
    77  
    78  	d := &i2cDevice{
    79  		location:    location,
    80  		sys:         a.sys,
    81  		fs:          a.fs,
    82  		lastAddress: -1,
    83  	}
    84  	return d, nil
    85  }
    86  
    87  // Close closes the character device file and resets the lazy variables.
    88  func (d *i2cDevice) Close() error {
    89  	d.mutex.Lock()
    90  	defer d.mutex.Unlock()
    91  
    92  	d.funcs = 0
    93  	d.lastAddress = -1
    94  	if d.file != nil {
    95  		return d.file.Close()
    96  	}
    97  	return nil
    98  }
    99  
   100  // ReadByte reads a byte from the current register of an i2c device.
   101  func (d *i2cDevice) ReadByte(address int) (byte, error) {
   102  	d.mutex.Lock()
   103  	defer d.mutex.Unlock()
   104  
   105  	if err := d.queryFunctionality(I2C_FUNC_SMBUS_READ_BYTE, "read byte"); err != nil {
   106  		return 0, err
   107  	}
   108  
   109  	var data uint8 = 0xFC // set value for debugging purposes
   110  	err := d.smbusAccess(address, I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, unsafe.Pointer(&data))
   111  	return data, err
   112  }
   113  
   114  // ReadByteData reads a byte from the given register of an i2c device.
   115  func (d *i2cDevice) ReadByteData(address int, reg uint8) (val uint8, err error) {
   116  	d.mutex.Lock()
   117  	defer d.mutex.Unlock()
   118  
   119  	if err := d.queryFunctionality(I2C_FUNC_SMBUS_READ_BYTE_DATA, "read byte data"); err != nil {
   120  		return 0, err
   121  	}
   122  
   123  	var data uint8 = 0xFD // set value for debugging purposes
   124  	err = d.smbusAccess(address, I2C_SMBUS_READ, reg, I2C_SMBUS_BYTE_DATA, unsafe.Pointer(&data))
   125  	return data, err
   126  }
   127  
   128  // ReadWordData reads a 16 bit value starting from the given register of an i2c device.
   129  func (d *i2cDevice) ReadWordData(address int, reg uint8) (val uint16, err error) {
   130  	d.mutex.Lock()
   131  	defer d.mutex.Unlock()
   132  
   133  	if err := d.queryFunctionality(I2C_FUNC_SMBUS_READ_WORD_DATA, "read word data"); err != nil {
   134  		return 0, err
   135  	}
   136  
   137  	var data uint16 = 0xFFFE // set value for debugging purposes
   138  	err = d.smbusAccess(address, I2C_SMBUS_READ, reg, I2C_SMBUS_WORD_DATA, unsafe.Pointer(&data))
   139  	return data, err
   140  }
   141  
   142  // ReadBlockData fills the given buffer with reads starting from the given register of an i2c device.
   143  func (d *i2cDevice) ReadBlockData(address int, reg uint8, data []byte) error {
   144  	d.mutex.Lock()
   145  	defer d.mutex.Unlock()
   146  
   147  	dataLen := len(data)
   148  	if dataLen > 32 {
   149  		return fmt.Errorf("Reading blocks larger than 32 bytes (%v) not supported", len(data))
   150  	}
   151  
   152  	data[0] = 0xFF // set value for debugging purposes
   153  	if err := d.queryFunctionality(I2C_FUNC_SMBUS_READ_I2C_BLOCK, "read block data"); err != nil {
   154  		if i2cDeviceDebug {
   155  			log.Printf("%s, use fallback\n", err.Error())
   156  		}
   157  		return d.readBlockDataFallback(address, reg, data)
   158  	}
   159  
   160  	// set the first element with the data size
   161  	buf := make([]byte, dataLen+1)
   162  	buf[0] = byte(dataLen)
   163  	copy(buf[1:], data)
   164  	if err := d.smbusAccess(address, I2C_SMBUS_READ, reg, I2C_SMBUS_I2C_BLOCK_DATA, unsafe.Pointer(&buf[0])); err != nil {
   165  		return err
   166  	}
   167  	// get data from buffer without first size element
   168  	copy(data, buf[1:])
   169  	return nil
   170  }
   171  
   172  // WriteByte writes the given byte value to the current register of an i2c device.
   173  func (d *i2cDevice) WriteByte(address int, val byte) error {
   174  	d.mutex.Lock()
   175  	defer d.mutex.Unlock()
   176  
   177  	if err := d.queryFunctionality(I2C_FUNC_SMBUS_WRITE_BYTE, "write byte"); err != nil {
   178  		return err
   179  	}
   180  
   181  	return d.smbusAccess(address, I2C_SMBUS_WRITE, val, I2C_SMBUS_BYTE, nil)
   182  }
   183  
   184  // WriteByteData writes the given byte value to the given register of an i2c device.
   185  func (d *i2cDevice) WriteByteData(address int, reg uint8, val uint8) error {
   186  	d.mutex.Lock()
   187  	defer d.mutex.Unlock()
   188  
   189  	if err := d.queryFunctionality(I2C_FUNC_SMBUS_WRITE_BYTE_DATA, "write byte data"); err != nil {
   190  		return err
   191  	}
   192  
   193  	var data = val
   194  	return d.smbusAccess(address, I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE_DATA, unsafe.Pointer(&data))
   195  }
   196  
   197  // WriteWordData writes the given 16 bit value starting from the given register of an i2c device.
   198  func (d *i2cDevice) WriteWordData(address int, reg uint8, val uint16) error {
   199  	d.mutex.Lock()
   200  	defer d.mutex.Unlock()
   201  
   202  	if err := d.queryFunctionality(I2C_FUNC_SMBUS_WRITE_WORD_DATA, "write word data"); err != nil {
   203  		return err
   204  	}
   205  
   206  	var data = val
   207  	return d.smbusAccess(address, I2C_SMBUS_WRITE, reg, I2C_SMBUS_WORD_DATA, unsafe.Pointer(&data))
   208  }
   209  
   210  // WriteBlockData writes the given buffer starting from the given register of an i2c device.
   211  func (d *i2cDevice) WriteBlockData(address int, reg uint8, data []byte) error {
   212  	d.mutex.Lock()
   213  	defer d.mutex.Unlock()
   214  
   215  	dataLen := len(data)
   216  	if dataLen > 32 {
   217  		return fmt.Errorf("Writing blocks larger than 32 bytes (%v) not supported", len(data))
   218  	}
   219  
   220  	if err := d.queryFunctionality(I2C_FUNC_SMBUS_WRITE_I2C_BLOCK, "write i2c block"); err != nil {
   221  		if i2cDeviceDebug {
   222  			log.Printf("%s, use fallback\n", err.Error())
   223  		}
   224  		return d.writeBlockDataFallback(address, reg, data)
   225  	}
   226  
   227  	// set the first element with the data size
   228  	buf := make([]byte, dataLen+1)
   229  	buf[0] = byte(dataLen)
   230  	copy(buf[1:], data)
   231  
   232  	return d.smbusAccess(address, I2C_SMBUS_WRITE, reg, I2C_SMBUS_I2C_BLOCK_DATA, unsafe.Pointer(&buf[0]))
   233  }
   234  
   235  // WriteBytes writes the given buffer starting from the current register of an i2c device.
   236  func (d *i2cDevice) WriteBytes(address int, data []byte) error {
   237  	d.mutex.Lock()
   238  	defer d.mutex.Unlock()
   239  
   240  	return d.writeBytes(address, data)
   241  }
   242  
   243  // Read implements direct I2C read operations.
   244  func (d *i2cDevice) Read(address int, b []byte) (n int, err error) {
   245  	d.mutex.Lock()
   246  	defer d.mutex.Unlock()
   247  
   248  	return d.read(address, b)
   249  }
   250  
   251  // Write implements the io.ReadWriteCloser method by direct I2C write operations.
   252  func (d *i2cDevice) Write(address int, b []byte) (n int, err error) {
   253  	d.mutex.Lock()
   254  	defer d.mutex.Unlock()
   255  
   256  	return d.write(address, b)
   257  }
   258  
   259  func (d *i2cDevice) readBlockDataFallback(address int, reg uint8, data []byte) error {
   260  	if err := d.writeBytes(address, []byte{reg}); err != nil {
   261  		return err
   262  	}
   263  	if err := d.readAndCheckCount(address, data); err != nil {
   264  		return err
   265  	}
   266  	return nil
   267  }
   268  
   269  func (d *i2cDevice) writeBlockDataFallback(address int, reg uint8, data []byte) error {
   270  	buf := make([]byte, len(data)+1)
   271  	copy(buf[1:], data)
   272  	buf[0] = reg
   273  
   274  	if err := d.writeBytes(address, buf); err != nil {
   275  		return err
   276  	}
   277  	return nil
   278  }
   279  
   280  func (d *i2cDevice) writeBytes(address int, data []byte) error {
   281  	n, err := d.write(address, data)
   282  	if err != nil {
   283  		return err
   284  	}
   285  	if n != len(data) {
   286  		return fmt.Errorf("Write %v bytes to device by sysfs, expected %v", n, len(data))
   287  	}
   288  	return nil
   289  }
   290  
   291  func (d *i2cDevice) write(address int, b []byte) (n int, err error) {
   292  	if err = d.setAddress(address); err != nil {
   293  		return 0, err
   294  	}
   295  	if err := d.openFileLazy("Write"); err != nil {
   296  		return 0, err
   297  	}
   298  	return d.file.Write(b)
   299  }
   300  
   301  func (d *i2cDevice) readAndCheckCount(address int, data []byte) error {
   302  	n, err := d.read(address, data)
   303  	if err != nil {
   304  		return err
   305  	}
   306  	if n != len(data) {
   307  		return fmt.Errorf("Read %v bytes from device by sysfs, expected %v", n, len(data))
   308  	}
   309  	return nil
   310  }
   311  
   312  func (d *i2cDevice) read(address int, b []byte) (n int, err error) {
   313  	if err = d.setAddress(address); err != nil {
   314  		return 0, err
   315  	}
   316  	if err := d.openFileLazy("Read"); err != nil {
   317  		return 0, err
   318  	}
   319  
   320  	return d.file.Read(b)
   321  }
   322  
   323  func (d *i2cDevice) queryFunctionality(requested uint64, sender string) error {
   324  	// lazy initialization
   325  	if d.funcs == 0 {
   326  		if err := d.syscallIoctl(I2C_FUNCS, unsafe.Pointer(&d.funcs), "Querying functionality"); err != nil {
   327  			return err
   328  		}
   329  	}
   330  
   331  	if d.funcs&requested == 0 {
   332  		return fmt.Errorf("SMBus %s not supported", sender)
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  func (d *i2cDevice) smbusAccess(address int, readWrite byte, command byte, protocol uint32, dataStart unsafe.Pointer) error {
   339  	if err := d.setAddress(address); err != nil {
   340  		return err
   341  	}
   342  
   343  	smbus := i2cSmbusIoctlData{
   344  		readWrite: readWrite,
   345  		command:   command,
   346  		protocol:  protocol,
   347  		data:      dataStart, // the reflected value of unsafePointer equals uintptr(dataStart),
   348  	}
   349  
   350  	sender := fmt.Sprintf("SMBus access r/w: %d, command: %d, protocol: %d, address: %d", readWrite, command, protocol, d.lastAddress)
   351  	if err := d.syscallIoctl(I2C_SMBUS, unsafe.Pointer(&smbus), sender); err != nil {
   352  		return err
   353  	}
   354  
   355  	return nil
   356  }
   357  
   358  // setAddress sets the address of the i2c device to use.
   359  func (d *i2cDevice) setAddress(address int) error {
   360  	if d.lastAddress == address && !forceSetAddress {
   361  		if i2cDeviceDebug {
   362  			log.Printf("I2C address %d was already sent - skip", address)
   363  		}
   364  		return nil
   365  	}
   366  	// for go vet false positives, see: https://github.com/golang/go/issues/41205
   367  	if err := d.syscallIoctl(I2C_SLAVE, unsafe.Pointer(uintptr(byte(address))), "Setting address"); err != nil {
   368  		return err
   369  	}
   370  	d.lastAddress = address
   371  	return nil
   372  }
   373  
   374  func (d *i2cDevice) syscallIoctl(signal uintptr, payload unsafe.Pointer, sender string) (err error) {
   375  	if err := d.openFileLazy(sender); err != nil {
   376  		return err
   377  	}
   378  	if _, _, errno := d.sys.syscall(syscall.SYS_IOCTL, d.file, signal, payload); errno != 0 {
   379  		return fmt.Errorf("%s failed with syscall.Errno %v", sender, errno)
   380  	}
   381  
   382  	return nil
   383  }
   384  
   385  func (d *i2cDevice) openFileLazy(sender string) (err error) {
   386  	// lazy initialization
   387  	// note: "os.ModeExclusive" is undefined without create the file. This means for the existing character device,
   388  	// a second open will not return an error e.g. due to a busy resource, so most likely "os.ModeExclusive" is not really
   389  	// helpful and we drop it to the default "0" used by normal Open().
   390  	if d.file == nil {
   391  		if d.file, err = d.fs.openFile(d.location, os.O_RDWR, 0); err != nil {
   392  			return err
   393  		}
   394  	}
   395  	return nil
   396  }