PKGBUILD/vendor/periph.io/x/periph/host/sysfs/i2c.go

389 lines
10 KiB
Go
Raw Normal View History

2018-12-04 18:11:50 +00:00
// Copyright 2016 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package sysfs
import (
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"unsafe"
"periph.io/x/periph"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpioreg"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/i2c/i2creg"
"periph.io/x/periph/conn/physic"
)
// I2CSetSpeedHook can be set by a driver to enable changing the I²C buses
// speed.
func I2CSetSpeedHook(h func(f physic.Frequency) error) error {
if h == nil {
return errors.New("sysfs-i2c: hook must not be nil")
}
drvI2C.mu.Lock()
defer drvI2C.mu.Unlock()
if drvI2C.setSpeed != nil {
return errors.New("sysfs-i2c: a speed hook was already set")
}
drvI2C.setSpeed = h
return nil
}
// NewI2C opens an I²C bus via its sysfs interface as described at
// https://www.kernel.org/doc/Documentation/i2c/dev-interface.
//
// busNumber is the bus number as exported by sysfs. For example if the path is
// /dev/i2c-1, busNumber should be 1.
//
// The resulting object is safe for concurent use.
//
// Do not use sysfs.NewI2C() directly as the package sysfs is providing a
// https://periph.io/x/periph/conn/i2c Linux-specific implementation.
//
// periph.io works on many OSes!
//
// Instead, use https://periph.io/x/periph/conn/i2c/i2creg#Open. This permits
// it to work on all operating systems, or devices like I²C over USB.
func NewI2C(busNumber int) (*I2C, error) {
if isLinux {
return newI2C(busNumber)
}
return nil, errors.New("sysfs-i2c: is not supported on this platform")
}
// I2C is an open I²C bus via sysfs.
//
// It can be used to communicate with multiple devices from multiple goroutines.
type I2C struct {
f ioctlCloser
busNumber int
mu sync.Mutex // In theory the kernel probably has an internal lock but not taking any chance.
fn functionality
scl gpio.PinIO
sda gpio.PinIO
}
// Close closes the handle to the I²C driver. It is not a requirement to close
// before process termination.
func (i *I2C) Close() error {
i.mu.Lock()
defer i.mu.Unlock()
if err := i.f.Close(); err != nil {
return fmt.Errorf("sysfs-i2c: %v", err)
}
return nil
}
func (i *I2C) String() string {
return fmt.Sprintf("I2C%d", i.busNumber)
}
// Tx execute a transaction as a single operation unit.
func (i *I2C) Tx(addr uint16, w, r []byte) error {
if addr >= 0x400 || (addr >= 0x80 && i.fn&func10BitAddr == 0) {
return errors.New("sysfs-i2c: invalid address")
}
if len(w) == 0 && len(r) == 0 {
return nil
}
// Convert the messages to the internal format.
var buf [2]i2cMsg
msgs := buf[0:0]
if len(w) != 0 {
msgs = buf[:1]
buf[0].addr = addr
buf[0].length = uint16(len(w))
buf[0].buf = uintptr(unsafe.Pointer(&w[0]))
}
if len(r) != 0 {
l := len(msgs)
msgs = msgs[:l+1] // extend the slice by one
buf[l].addr = addr
buf[l].flags = flagRD
buf[l].length = uint16(len(r))
buf[l].buf = uintptr(unsafe.Pointer(&r[0]))
}
p := rdwrIoctlData{
msgs: uintptr(unsafe.Pointer(&msgs[0])),
nmsgs: uint32(len(msgs)),
}
pp := uintptr(unsafe.Pointer(&p))
i.mu.Lock()
defer i.mu.Unlock()
if err := i.f.Ioctl(ioctlRdwr, pp); err != nil {
return fmt.Errorf("sysfs-i2c: %v", err)
}
return nil
}
// SetSpeed implements i2c.Bus.
func (i *I2C) SetSpeed(f physic.Frequency) error {
if f > 100*physic.MegaHertz {
return fmt.Errorf("sysfs-i2c: invalid speed %s; maximum supported clock is 100MHz", f)
}
if f < physic.KiloHertz {
return fmt.Errorf("sysfs-i2c: invalid speed %s; minimum supported clock is 1KHz; did you forget to multiply by physic.KiloHertz?", f)
}
drvI2C.mu.Lock()
defer drvI2C.mu.Unlock()
if drvI2C.setSpeed != nil {
return drvI2C.setSpeed(f)
}
return errors.New("sysfs-i2c: not supported")
}
// SCL implements i2c.Pins.
func (i *I2C) SCL() gpio.PinIO {
i.initPins()
return i.scl
}
// SDA implements i2c.Pins.
func (i *I2C) SDA() gpio.PinIO {
i.initPins()
return i.sda
}
// Private details.
func newI2C(busNumber int) (*I2C, error) {
// Use the devfs path for now instead of sysfs path.
f, err := ioctlOpen(fmt.Sprintf("/dev/i2c-%d", busNumber), os.O_RDWR)
if err != nil {
// Try to be helpful here. There are generally two cases:
// - /dev/i2c-X doesn't exist. In this case, /boot/config.txt has to be
// edited to enable I²C then the device must be rebooted.
// - permission denied. In this case, the user has to be added to plugdev.
if os.IsNotExist(err) {
return nil, fmt.Errorf("sysfs-i2c: bus #%d is not configured: %v", busNumber, err)
}
// TODO(maruel): This is a debianism.
return nil, fmt.Errorf("sysfs-i2c: are you member of group 'plugdev'? %v", err)
}
i := &I2C{f: f, busNumber: busNumber}
// TODO(maruel): Changing the speed is currently doing this for all devices.
// https://github.com/raspberrypi/linux/issues/215
// Need to access /sys/module/i2c_bcm2708/parameters/baudrate
// Query to know if 10 bits addresses are supported.
if err = i.f.Ioctl(ioctlFuncs, uintptr(unsafe.Pointer(&i.fn))); err != nil {
return nil, fmt.Errorf("sysfs-i2c: %v", err)
}
return i, nil
}
func (i *I2C) initPins() {
i.mu.Lock()
if i.scl == nil {
if i.scl = gpioreg.ByName(fmt.Sprintf("I2C%d_SCL", i.busNumber)); i.scl == nil {
i.scl = gpio.INVALID
}
if i.sda = gpioreg.ByName(fmt.Sprintf("I2C%d_SDA", i.busNumber)); i.sda == nil {
i.sda = gpio.INVALID
}
}
i.mu.Unlock()
}
// i2cdev driver IOCTL control codes.
//
// Constants and structure definition can be found at
// /usr/include/linux/i2c-dev.h and /usr/include/linux/i2c.h.
const (
ioctlRetries = 0x701 // TODO(maruel): Expose this
ioctlTimeout = 0x702 // TODO(maruel): Expose this; in units of 10ms
ioctlSlave = 0x703
ioctlTenBits = 0x704 // TODO(maruel): Expose this but the header says it's broken (!?)
ioctlFuncs = 0x705
ioctlRdwr = 0x707
)
// flags
const (
flagTEN = 0x0010 // this is a ten bit chip address
flagRD = 0x0001 // read data, from slave to master
flagSTOP = 0x8000 // if funcProtocolMangling
flagNOSTART = 0x4000 // if I2C_FUNC_NOSTART
flagRevDirAddr = 0x2000 // if funcProtocolMangling
flagIgnoreNAK = 0x1000 // if funcProtocolMangling
flagNoRDACK = 0x0800 // if funcProtocolMangling
flagRecvLen = 0x0400 // length will be first received byte
)
type functionality uint64
const (
funcI2C = 0x00000001
func10BitAddr = 0x00000002
funcProtocolMangling = 0x00000004 // I2C_M_IGNORE_NAK etc.
funcSMBusPEC = 0x00000008
funcNOSTART = 0x00000010 // I2C_M_NOSTART
funcSMBusBlockProcCall = 0x00008000 // SMBus 2.0
funcSMBusQuick = 0x00010000
funcSMBusReadByte = 0x00020000
funcSMBusWriteByte = 0x00040000
funcSMBusReadByteData = 0x00080000
funcSMBusWriteByteData = 0x00100000
funcSMBusReadWordData = 0x00200000
funcSMBusWriteWordData = 0x00400000
funcSMBusProcCall = 0x00800000
funcSMBusReadBlockData = 0x01000000
funcSMBusWriteBlockData = 0x02000000
funcSMBusReadI2CBlock = 0x04000000 // I2C-like block xfer
funcSMBusWriteI2CBlock = 0x08000000 // w/ 1-byte reg. addr.
)
func (f functionality) String() string {
var out []string
if f&funcI2C != 0 {
out = append(out, "I2C")
}
if f&func10BitAddr != 0 {
out = append(out, "10BIT_ADDR")
}
if f&funcProtocolMangling != 0 {
out = append(out, "PROTOCOL_MANGLING")
}
if f&funcSMBusPEC != 0 {
out = append(out, "SMBUS_PEC")
}
if f&funcNOSTART != 0 {
out = append(out, "NOSTART")
}
if f&funcSMBusBlockProcCall != 0 {
out = append(out, "SMBUS_BLOCK_PROC_CALL")
}
if f&funcSMBusQuick != 0 {
out = append(out, "SMBUS_QUICK")
}
if f&funcSMBusReadByte != 0 {
out = append(out, "SMBUS_READ_BYTE")
}
if f&funcSMBusWriteByte != 0 {
out = append(out, "SMBUS_WRITE_BYTE")
}
if f&funcSMBusReadByteData != 0 {
out = append(out, "SMBUS_READ_BYTE_DATA")
}
if f&funcSMBusWriteByteData != 0 {
out = append(out, "SMBUS_WRITE_BYTE_DATA")
}
if f&funcSMBusReadWordData != 0 {
out = append(out, "SMBUS_READ_WORD_DATA")
}
if f&funcSMBusWriteWordData != 0 {
out = append(out, "SMBUS_WRITE_WORD_DATA")
}
if f&funcSMBusProcCall != 0 {
out = append(out, "SMBUS_PROC_CALL")
}
if f&funcSMBusReadBlockData != 0 {
out = append(out, "SMBUS_READ_BLOCK_DATA")
}
if f&funcSMBusWriteBlockData != 0 {
out = append(out, "SMBUS_WRITE_BLOCK_DATA")
}
if f&funcSMBusReadI2CBlock != 0 {
out = append(out, "SMBUS_READ_I2C_BLOCK")
}
if f&funcSMBusWriteI2CBlock != 0 {
out = append(out, "SMBUS_WRITE_I2C_BLOCK")
}
return strings.Join(out, "|")
}
type rdwrIoctlData struct {
msgs uintptr // Pointer to i2cMsg
nmsgs uint32
}
type i2cMsg struct {
addr uint16 // Address to communicate with
flags uint16 // 1 for read, see i2c.h for more details
length uint16
buf uintptr
}
//
// driverI2C implements periph.Driver.
type driverI2C struct {
mu sync.Mutex
buses []string
setSpeed func(f physic.Frequency) error
}
func (d *driverI2C) String() string {
return "sysfs-i2c"
}
func (d *driverI2C) Prerequisites() []string {
return nil
}
func (d *driverI2C) After() []string {
return nil
}
func (d *driverI2C) Init() (bool, error) {
// Do not use "/sys/bus/i2c/devices/i2c-" as Raspbian's provided udev rules
// only modify the ACL of /dev/i2c-* but not the ones in /sys/bus/...
prefix := "/dev/i2c-"
items, err := filepath.Glob(prefix + "*")
if err != nil {
return true, err
}
if len(items) == 0 {
return false, errors.New("no I²C bus found")
}
// Make sure they are registered in order.
sort.Strings(items)
for _, item := range items {
bus, err := strconv.Atoi(item[len(prefix):])
if err != nil {
continue
}
name := fmt.Sprintf("/dev/i2c-%d", bus)
d.buses = append(d.buses, name)
aliases := []string{fmt.Sprintf("I2C%d", bus)}
if err := i2creg.Register(name, aliases, bus, openerI2C(bus).Open); err != nil {
return true, err
}
}
return true, nil
}
type openerI2C int
func (o openerI2C) Open() (i2c.BusCloser, error) {
b, err := NewI2C(int(o))
if err != nil {
return nil, err
}
return b, nil
}
func init() {
if isLinux {
periph.MustRegister(&drvI2C)
}
}
var drvI2C driverI2C
var _ i2c.Bus = &I2C{}
var _ i2c.BusCloser = &I2C{}