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 }