build: upgrade to go 1.17 and dependencies
This commit is contained in:
13
vendor/periph.io/x/host/v3/sysfs/doc.go
generated
vendored
Normal file
13
vendor/periph.io/x/host/v3/sysfs/doc.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// 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 implements a sane library to interact with sysfs provided
|
||||
// hardware access.
|
||||
//
|
||||
// sysfs a virtual file system rooted at /sys/.
|
||||
//
|
||||
// This package also include drivers using devfs.
|
||||
//
|
||||
// https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
|
||||
package sysfs
|
319
vendor/periph.io/x/host/v3/sysfs/fs_linux.go
generated
vendored
Normal file
319
vendor/periph.io/x/host/v3/sysfs/fs_linux.go
generated
vendored
Normal file
@ -0,0 +1,319 @@
|
||||
// Copyright 2017 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"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// syscall.EpollCtl() commands.
|
||||
//
|
||||
// These are defined here so we don't need to import golang.org/x/sys/unix.
|
||||
//
|
||||
// http://man7.org/linux/man-pages/man2/epoll_ctl.2.html
|
||||
const (
|
||||
epollCTLAdd = 1 // EPOLL_CTL_ADD
|
||||
epollCTLDel = 2 // EPOLL_CTL_DEL
|
||||
epollCTLMod = 3 // EPOLL_CTL_MOD
|
||||
)
|
||||
|
||||
// Bitmask for field syscall.EpollEvent.Events.
|
||||
//
|
||||
// These are defined here so we don't need to import golang.org/x/sys/unix.
|
||||
//
|
||||
// http://man7.org/linux/man-pages/man2/epoll_ctl.2.html
|
||||
type epollEvent uint32
|
||||
|
||||
const (
|
||||
epollIN epollEvent = 0x1 // EPOLLIN: available for read
|
||||
epollOUT epollEvent = 0x4 // EPOLLOUT: available for write
|
||||
epollPRI epollEvent = 0x2 // EPOLLPRI: exceptional urgent condition
|
||||
epollERR epollEvent = 0x8 // EPOLLERR: error
|
||||
epollHUP epollEvent = 0x10 // EPOLLHUP: hangup
|
||||
epollET epollEvent = 0x80000000 // EPOLLET: Edge Triggered behavior
|
||||
epollONESHOT epollEvent = 0x40000000 // EPOLLONESHOT: One shot
|
||||
epollWAKEUP epollEvent = 0x20000000 // EPOLLWAKEUP: disable system sleep; kernel >=3.5
|
||||
epollEXCLUSIVE epollEvent = 0x10000000 // EPOLLEXCLUSIVE: only wake one; kernel >=4.5
|
||||
)
|
||||
|
||||
var bitmaskString = [...]struct {
|
||||
e epollEvent
|
||||
s string
|
||||
}{
|
||||
{epollIN, "IN"},
|
||||
{epollOUT, "OUT"},
|
||||
{epollPRI, "PRI"},
|
||||
{epollERR, "ERR"},
|
||||
{epollHUP, "HUP"},
|
||||
{epollET, "ET"},
|
||||
{epollONESHOT, "ONESHOT"},
|
||||
{epollWAKEUP, "WAKEUP"},
|
||||
{epollEXCLUSIVE, "EXCLUSIVE"},
|
||||
}
|
||||
|
||||
// String is useful for debugging.
|
||||
func (e epollEvent) String() string {
|
||||
var out []string
|
||||
for _, b := range bitmaskString {
|
||||
if e&b.e != 0 {
|
||||
out = append(out, b.s)
|
||||
e &^= b.e
|
||||
}
|
||||
}
|
||||
if e != 0 {
|
||||
out = append(out, "0x"+strconv.FormatUint(uint64(e), 16))
|
||||
}
|
||||
if len(out) == 0 {
|
||||
out = []string{"0"}
|
||||
}
|
||||
return strings.Join(out, "|")
|
||||
}
|
||||
|
||||
// eventsListener listens for events for multiple files as a single system call.
|
||||
//
|
||||
// One OS thread is needed for all the events. This is more efficient on single
|
||||
// core system.
|
||||
type eventsListener struct {
|
||||
// Atomic value set to one once fully initialized.
|
||||
initialized int32
|
||||
|
||||
// Mapping of file descriptors to wait on with their corresponding channels.
|
||||
mu sync.Mutex
|
||||
// File descriptor of the epoll handle itself.
|
||||
epollFd int
|
||||
// Pipes to wake up the EpollWait() system call inside loop().
|
||||
r, w *os.File
|
||||
// Return channel to confirm that EpollWait() was woken up.
|
||||
wakeUp <-chan time.Time
|
||||
// Map of file handles to user listening channel.
|
||||
fds map[int32]chan<- time.Time
|
||||
}
|
||||
|
||||
// init must be called on a fresh instance.
|
||||
func (e *eventsListener) init() error {
|
||||
if atomic.LoadInt32(&e.initialized) != 0 {
|
||||
// Was already initialized.
|
||||
return nil
|
||||
}
|
||||
e.mu.Lock()
|
||||
if atomic.LoadInt32(&e.initialized) != 0 {
|
||||
// Was already initialized, but this was done concurrently with another
|
||||
// thread.
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
err := e.initLocked()
|
||||
atomic.StoreInt32(&e.initialized, 1)
|
||||
if err != nil {
|
||||
e.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
wakeUp := make(chan time.Time)
|
||||
e.wakeUp = wakeUp
|
||||
e.fds = map[int32]chan<- time.Time{}
|
||||
e.fds[int32(e.r.Fd())] = wakeUp
|
||||
// The mutex is still held after this function exits, it's loop() that will
|
||||
// release the mutex.
|
||||
//
|
||||
// This forces loop() to be started before addFd() can be called by users.
|
||||
go e.loop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *eventsListener) initLocked() error {
|
||||
var err error
|
||||
e.epollFd, err = syscall.EpollCreate(1)
|
||||
switch {
|
||||
case err == nil:
|
||||
break
|
||||
case err.Error() == "function not implemented":
|
||||
// Some arch (arm64) do not implement EpollCreate().
|
||||
if e.epollFd, err = syscall.EpollCreate1(0); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return err
|
||||
}
|
||||
e.r, e.w, err = os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Only need epollIN. epollPRI has no effect on pipes.
|
||||
return e.addFdInner(e.r.Fd(), epollET|epollIN)
|
||||
}
|
||||
|
||||
// loop is the main event loop.
|
||||
func (e *eventsListener) loop() {
|
||||
var events []syscall.EpollEvent
|
||||
type lookup struct {
|
||||
c chan<- time.Time
|
||||
event epollEvent
|
||||
}
|
||||
var lookups []lookup
|
||||
for first := true; ; {
|
||||
if !first {
|
||||
e.mu.Lock()
|
||||
}
|
||||
if len(events) < len(e.fds) {
|
||||
events = make([]syscall.EpollEvent, len(e.fds))
|
||||
}
|
||||
e.mu.Unlock()
|
||||
first = false
|
||||
|
||||
if len(events) == 0 {
|
||||
panic("internal error: there's should be at least one pipe")
|
||||
}
|
||||
|
||||
// http://man7.org/linux/man-pages/man2/epoll_wait.2.html
|
||||
n, err := syscall.EpollWait(e.epollFd, events, -1)
|
||||
if n <= 0 {
|
||||
// -1 if an error occurred (EBADF, EFAULT, EINVAL) or the call was
|
||||
// interrupted by a signal (EINTR).
|
||||
// 0 is the timeout occurred. In this case there's no timeout specified.
|
||||
// Still handle this explicitly in case a timeout could be triggered by
|
||||
// external events, like system sleep.
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
// TODO(maruel): It'd be nice to be able to surface this.
|
||||
// This may cause a busy loop. Hopefully the user will notice and will
|
||||
// fix their code.
|
||||
// This can happen when removeFd() is called, in this case silently
|
||||
// ignore the error.
|
||||
continue
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
// Create a look up table with the lock, so that then the channel can be
|
||||
// pushed to without the lock.
|
||||
if cap(lookups) < n {
|
||||
lookups = make([]lookup, 0, n)
|
||||
} else {
|
||||
lookups = lookups[:0]
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
for _, ev := range events[:n] {
|
||||
ep := epollEvent(ev.Events)
|
||||
// Skip over file descriptors that are not present.
|
||||
c, ok := e.fds[ev.Fd]
|
||||
if !ok {
|
||||
// That's a race condition where the file descriptor was removed by
|
||||
// removeFd() but it still triggered. Ignore this event.
|
||||
continue
|
||||
}
|
||||
// Look at the event to determine if it's worth sending a pulse. It's
|
||||
// maybe not worth it. Ignore epollERR, since it's always set for GPIO
|
||||
// sysfs.
|
||||
// Pipe and socket trigger epollIN and epollOUT, but GPIO sysfs triggers
|
||||
// epollPRI.
|
||||
if ep&(epollPRI|epollIN|epollOUT) != 0 {
|
||||
lookups = append(lookups, lookup{c: c, event: ep})
|
||||
}
|
||||
}
|
||||
e.mu.Unlock()
|
||||
|
||||
// Once the lock is released, send the timestamps.
|
||||
for _, t := range lookups {
|
||||
t.c <- now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addFd starts listening to events generated by file descriptor |fd|.
|
||||
//
|
||||
// fd is the OS file descriptor. In practice, it must fit a int32 value. It
|
||||
// works on pipes, sockets and sysfs objects like GPIO but not on standard
|
||||
// files.
|
||||
//
|
||||
// c is the channel to send events to. Unbuffered channel will block the event
|
||||
// loop, which may mean lost events, especially if multiple files are listened
|
||||
// to simultaneously.
|
||||
//
|
||||
// flags is the events to listen to. No need to specify epollERR and epollHUP,
|
||||
// they are sent anyway.
|
||||
//
|
||||
// addFd lazy initializes eventsListener if it was not initialized yet.
|
||||
//
|
||||
// It can fail due to various reasons, a few are:
|
||||
// ENOSPC: /proc/sys/fs/epoll/max_user_watches limit was exceeded
|
||||
// ENOMEM: No memory available
|
||||
// EPERM: fd is a regular file or directory
|
||||
func (e *eventsListener) addFd(fd uintptr, c chan<- time.Time, flags epollEvent) error {
|
||||
if c == nil {
|
||||
return errors.New("fd: addFd requires a valid channel")
|
||||
}
|
||||
if err := e.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := e.addFdInner(fd, flags); err != nil {
|
||||
return err
|
||||
}
|
||||
e.mu.Lock()
|
||||
e.fds[int32(fd)] = c
|
||||
e.mu.Unlock()
|
||||
// Wake up the poller so it notices there's one new file.
|
||||
e.wakeUpLoop(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *eventsListener) addFdInner(fd uintptr, flags epollEvent) error {
|
||||
ev := syscall.EpollEvent{Events: uint32(flags), Fd: int32(fd)}
|
||||
return syscall.EpollCtl(e.epollFd, epollCTLAdd, int(fd), &ev)
|
||||
}
|
||||
|
||||
// removeFd stop listening to events on this file descriptor.
|
||||
func (e *eventsListener) removeFd(fd uintptr) error {
|
||||
if err := syscall.EpollCtl(e.epollFd, epollCTLDel, int(fd), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
e.mu.Lock()
|
||||
delete(e.fds, int32(fd))
|
||||
e.mu.Unlock()
|
||||
// Wake up the poller so it notices there's one less file.
|
||||
e.wakeUpLoop(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// wakeUpLoop wakes up the poller and waits for it.
|
||||
//
|
||||
// Must not be called with the lock held.
|
||||
func (e *eventsListener) wakeUpLoop(c <-chan time.Time) time.Time {
|
||||
// TODO(maruel): Figure out a way to wake up that doesn't require emptying.
|
||||
var b [1]byte
|
||||
_, _ = e.w.Write(b[:])
|
||||
var t time.Time
|
||||
if c != nil {
|
||||
// To prevent deadlock, also empty c.
|
||||
for {
|
||||
select {
|
||||
case <-c:
|
||||
case t = <-e.wakeUp:
|
||||
goto out
|
||||
}
|
||||
}
|
||||
out:
|
||||
} else {
|
||||
t = <-e.wakeUp
|
||||
}
|
||||
// Don't forget to empty the pipe. Sadly, this will wake up the loop a second
|
||||
// time.
|
||||
_, _ = e.r.Read(b[:])
|
||||
return t
|
||||
}
|
||||
|
||||
// events is the global events listener.
|
||||
//
|
||||
// It uses a single global goroutine lazily initialized to call
|
||||
// syscall.EpollWait() to listen to many file descriptors at once.
|
||||
var events eventsListener
|
15
vendor/periph.io/x/host/v3/sysfs/fs_other.go
generated
vendored
Normal file
15
vendor/periph.io/x/host/v3/sysfs/fs_other.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package sysfs
|
||||
|
||||
type eventsListener struct {
|
||||
}
|
||||
|
||||
// events is the global events listener.
|
||||
//
|
||||
// It is not used outside linux.
|
||||
var events eventsListener
|
520
vendor/periph.io/x/host/v3/sysfs/gpio.go
generated
vendored
Normal file
520
vendor/periph.io/x/host/v3/sysfs/gpio.go
generated
vendored
Normal file
@ -0,0 +1,520 @@
|
||||
// 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"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3"
|
||||
"periph.io/x/conn/v3/driver/driverreg"
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/gpio/gpioreg"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
"periph.io/x/conn/v3/pin"
|
||||
"periph.io/x/host/v3/fs"
|
||||
)
|
||||
|
||||
// Pins is all the pins exported by GPIO sysfs.
|
||||
//
|
||||
// Some CPU architectures have the pin numbers start at 0 and use consecutive
|
||||
// pin numbers but this is not the case for all CPU architectures, some
|
||||
// have gaps in the pin numbering.
|
||||
//
|
||||
// This global variable is initialized once at driver initialization and isn't
|
||||
// mutated afterward. Do not modify it.
|
||||
var Pins map[int]*Pin
|
||||
|
||||
// Pin represents one GPIO pin as found by sysfs.
|
||||
type Pin struct {
|
||||
number int
|
||||
name string
|
||||
root string // Something like /sys/class/gpio/gpio%d/
|
||||
|
||||
mu sync.Mutex
|
||||
err error // If open() failed
|
||||
direction direction // Cache of the last known direction
|
||||
edge gpio.Edge // Cache of the last edge used.
|
||||
fDirection fileIO // handle to /sys/class/gpio/gpio*/direction; never closed
|
||||
fEdge fileIO // handle to /sys/class/gpio/gpio*/edge; never closed
|
||||
fValue fileIO // handle to /sys/class/gpio/gpio*/value; never closed
|
||||
event fs.Event // Initialized once
|
||||
buf [4]byte // scratch buffer for Func(), Read() and Out()
|
||||
}
|
||||
|
||||
// String implements conn.Resource.
|
||||
func (p *Pin) String() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
// Halt implements conn.Resource.
|
||||
//
|
||||
// It stops edge detection if enabled.
|
||||
func (p *Pin) Halt() error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.haltEdge()
|
||||
}
|
||||
|
||||
// Name implements pin.Pin.
|
||||
func (p *Pin) Name() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
// Number implements pin.Pin.
|
||||
func (p *Pin) Number() int {
|
||||
return p.number
|
||||
}
|
||||
|
||||
// Function implements pin.Pin.
|
||||
func (p *Pin) Function() string {
|
||||
return string(p.Func())
|
||||
}
|
||||
|
||||
// Func implements pin.PinFunc.
|
||||
func (p *Pin) Func() pin.Func {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
// TODO(maruel): There's an internal bug which causes p.direction to be
|
||||
// invalid (!?) Need to figure it out ASAP.
|
||||
if err := p.open(); err != nil {
|
||||
return pin.FuncNone
|
||||
}
|
||||
if _, err := seekRead(p.fDirection, p.buf[:]); err != nil {
|
||||
return pin.FuncNone
|
||||
}
|
||||
if p.buf[0] == 'i' && p.buf[1] == 'n' {
|
||||
p.direction = dIn
|
||||
} else if p.buf[0] == 'o' && p.buf[1] == 'u' && p.buf[2] == 't' {
|
||||
p.direction = dOut
|
||||
}
|
||||
if p.direction == dIn {
|
||||
if p.Read() {
|
||||
return gpio.IN_HIGH
|
||||
}
|
||||
return gpio.IN_LOW
|
||||
} else if p.direction == dOut {
|
||||
if p.Read() {
|
||||
return gpio.OUT_HIGH
|
||||
}
|
||||
return gpio.OUT_LOW
|
||||
}
|
||||
return pin.FuncNone
|
||||
}
|
||||
|
||||
// SupportedFuncs implements pin.PinFunc.
|
||||
func (p *Pin) SupportedFuncs() []pin.Func {
|
||||
return []pin.Func{gpio.IN, gpio.OUT}
|
||||
}
|
||||
|
||||
// SetFunc implements pin.PinFunc.
|
||||
func (p *Pin) SetFunc(f pin.Func) error {
|
||||
switch f {
|
||||
case gpio.IN:
|
||||
return p.In(gpio.PullNoChange, gpio.NoEdge)
|
||||
case gpio.OUT_HIGH:
|
||||
return p.Out(gpio.High)
|
||||
case gpio.OUT, gpio.OUT_LOW:
|
||||
return p.Out(gpio.Low)
|
||||
default:
|
||||
return p.wrap(errors.New("unsupported function"))
|
||||
}
|
||||
}
|
||||
|
||||
// In implements gpio.PinIn.
|
||||
func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error {
|
||||
if pull != gpio.PullNoChange && pull != gpio.Float {
|
||||
return p.wrap(errors.New("doesn't support pull-up/pull-down"))
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.direction != dIn {
|
||||
if err := p.open(); err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
if err := seekWrite(p.fDirection, bIn); err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
p.direction = dIn
|
||||
}
|
||||
// Always push none to help accumulated flush edges. This is not fool proof
|
||||
// but it seems to help.
|
||||
if p.fEdge != nil {
|
||||
if err := seekWrite(p.fEdge, bNone); err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
}
|
||||
// Assume that when the pin was switched, the driver doesn't recall if edge
|
||||
// triggering was enabled.
|
||||
if edge != gpio.NoEdge {
|
||||
if p.fEdge == nil {
|
||||
var err error
|
||||
p.fEdge, err = fileIOOpen(p.root+"edge", os.O_RDWR)
|
||||
if err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
if err = p.event.MakeEvent(p.fValue.Fd()); err != nil {
|
||||
_ = p.fEdge.Close()
|
||||
p.fEdge = nil
|
||||
return p.wrap(err)
|
||||
}
|
||||
}
|
||||
// Always reset the edge detection mode to none after starting the epoll
|
||||
// otherwise edges are not always delivered, as observed on an Allwinner A20
|
||||
// running kernel 4.14.14.
|
||||
if err := seekWrite(p.fEdge, bNone); err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
var b []byte
|
||||
switch edge {
|
||||
case gpio.RisingEdge:
|
||||
b = bRising
|
||||
case gpio.FallingEdge:
|
||||
b = bFalling
|
||||
case gpio.BothEdges:
|
||||
b = bBoth
|
||||
}
|
||||
if err := seekWrite(p.fEdge, b); err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
}
|
||||
p.edge = edge
|
||||
// This helps to remove accumulated edges but this is not 100% sufficient.
|
||||
// Most of the time the interrupts are handled promptly enough that this loop
|
||||
// flushes the accumulated interrupt.
|
||||
// Sometimes the kernel may have accumulated interrupts that haven't been
|
||||
// processed for a long time, it can easily be >300µs even on a quite idle
|
||||
// CPU. In this case, the loop below is not sufficient, since the interrupt
|
||||
// will happen afterward "out of the blue".
|
||||
if edge != gpio.NoEdge {
|
||||
p.WaitForEdge(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read implements gpio.PinIn.
|
||||
func (p *Pin) Read() gpio.Level {
|
||||
// There's no lock here.
|
||||
if p.fValue == nil {
|
||||
return gpio.Low
|
||||
}
|
||||
if _, err := seekRead(p.fValue, p.buf[:]); err != nil {
|
||||
// Error.
|
||||
return gpio.Low
|
||||
}
|
||||
if p.buf[0] == '0' {
|
||||
return gpio.Low
|
||||
}
|
||||
if p.buf[0] == '1' {
|
||||
return gpio.High
|
||||
}
|
||||
// Error.
|
||||
return gpio.Low
|
||||
}
|
||||
|
||||
// WaitForEdge implements gpio.PinIn.
|
||||
func (p *Pin) WaitForEdge(timeout time.Duration) bool {
|
||||
// Run lockless, as the normal use is to call in a busy loop.
|
||||
var ms int
|
||||
if timeout == -1 {
|
||||
ms = -1
|
||||
} else {
|
||||
ms = int(timeout / time.Millisecond)
|
||||
}
|
||||
start := time.Now()
|
||||
for {
|
||||
if nr, err := p.event.Wait(ms); err != nil {
|
||||
return false
|
||||
} else if nr == 1 {
|
||||
// TODO(maruel): According to pigpio, the correct way to consume the
|
||||
// interrupt is to call Seek().
|
||||
return true
|
||||
}
|
||||
// A signal occurred.
|
||||
if timeout != -1 {
|
||||
ms = int((timeout - time.Since(start)) / time.Millisecond)
|
||||
}
|
||||
if ms <= 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pull implements gpio.PinIn.
|
||||
//
|
||||
// It returns gpio.PullNoChange since gpio sysfs has no support for input pull
|
||||
// resistor.
|
||||
func (p *Pin) Pull() gpio.Pull {
|
||||
return gpio.PullNoChange
|
||||
}
|
||||
|
||||
// DefaultPull implements gpio.PinIn.
|
||||
//
|
||||
// It returns gpio.PullNoChange since gpio sysfs has no support for input pull
|
||||
// resistor.
|
||||
func (p *Pin) DefaultPull() gpio.Pull {
|
||||
return gpio.PullNoChange
|
||||
}
|
||||
|
||||
// Out implements gpio.PinOut.
|
||||
func (p *Pin) Out(l gpio.Level) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.direction != dOut {
|
||||
if err := p.open(); err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
if err := p.haltEdge(); err != nil {
|
||||
return err
|
||||
}
|
||||
// "To ensure glitch free operation, values "low" and "high" may be written
|
||||
// to configure the GPIO as an output with that initial value."
|
||||
var d []byte
|
||||
if l == gpio.Low {
|
||||
d = bLow
|
||||
} else {
|
||||
d = bHigh
|
||||
}
|
||||
if err := seekWrite(p.fDirection, d); err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
p.direction = dOut
|
||||
return nil
|
||||
}
|
||||
if l == gpio.Low {
|
||||
p.buf[0] = '0'
|
||||
} else {
|
||||
p.buf[0] = '1'
|
||||
}
|
||||
if err := seekWrite(p.fValue, p.buf[:1]); err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PWM implements gpio.PinOut.
|
||||
//
|
||||
// This is not supported on sysfs.
|
||||
func (p *Pin) PWM(gpio.Duty, physic.Frequency) error {
|
||||
return p.wrap(errors.New("pwm is not supported via sysfs"))
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// open opens the gpio sysfs handle to /value and /direction.
|
||||
//
|
||||
// lock must be held.
|
||||
func (p *Pin) open() error {
|
||||
if p.fDirection != nil || p.err != nil {
|
||||
return p.err
|
||||
}
|
||||
|
||||
if drvGPIO.exportHandle == nil {
|
||||
return errors.New("sysfs gpio is not initialized")
|
||||
}
|
||||
|
||||
// Try to open the pin if it was there. It's possible it had been exported
|
||||
// already.
|
||||
if p.fValue, p.err = fileIOOpen(p.root+"value", os.O_RDWR); p.err == nil {
|
||||
// Fast track.
|
||||
goto direction
|
||||
} else if !os.IsNotExist(p.err) {
|
||||
// It exists but not accessible, not worth doing the remainder.
|
||||
p.err = fmt.Errorf("need more access, try as root or setup udev rules: %v", p.err)
|
||||
return p.err
|
||||
}
|
||||
|
||||
if _, p.err = drvGPIO.exportHandle.Write([]byte(strconv.Itoa(p.number))); p.err != nil && !isErrBusy(p.err) {
|
||||
if os.IsPermission(p.err) {
|
||||
p.err = fmt.Errorf("need more access, try as root or setup udev rules: %v", p.err)
|
||||
}
|
||||
return p.err
|
||||
}
|
||||
|
||||
// There's a race condition where the file may be created but udev is still
|
||||
// running the Raspbian udev rule to make it readable to the current user.
|
||||
// It's simpler to just loop a little as if /export is accessible, it doesn't
|
||||
// make sense that gpioN/value doesn't become accessible eventually.
|
||||
for start := time.Now(); time.Since(start) < 5*time.Second; {
|
||||
// The virtual file creation is synchronous when writing to /export; albeit
|
||||
// udev rule execution is asynchronous, so file mode change via udev rules
|
||||
// takes some time to propagate.
|
||||
if p.fValue, p.err = fileIOOpen(p.root+"value", os.O_RDWR); p.err == nil || !os.IsPermission(p.err) {
|
||||
// Either success or a failure that is not a permission error.
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.err != nil {
|
||||
return p.err
|
||||
}
|
||||
|
||||
direction:
|
||||
if p.fDirection, p.err = fileIOOpen(p.root+"direction", os.O_RDWR); p.err != nil {
|
||||
_ = p.fValue.Close()
|
||||
p.fValue = nil
|
||||
}
|
||||
return p.err
|
||||
}
|
||||
|
||||
// haltEdge stops any on-going edge detection.
|
||||
func (p *Pin) haltEdge() error {
|
||||
if p.edge != gpio.NoEdge {
|
||||
if err := seekWrite(p.fEdge, bNone); err != nil {
|
||||
return p.wrap(err)
|
||||
}
|
||||
p.edge = gpio.NoEdge
|
||||
// This is still important to remove an accumulated edge.
|
||||
p.WaitForEdge(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pin) wrap(err error) error {
|
||||
return fmt.Errorf("sysfs-gpio (%s): %v", p, err)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type direction int
|
||||
|
||||
const (
|
||||
dUnknown direction = 0
|
||||
dIn direction = 1
|
||||
dOut direction = 2
|
||||
)
|
||||
|
||||
var (
|
||||
bIn = []byte("in")
|
||||
bLow = []byte("low")
|
||||
bHigh = []byte("high")
|
||||
bNone = []byte("none")
|
||||
bRising = []byte("rising")
|
||||
bFalling = []byte("falling")
|
||||
bBoth = []byte("both")
|
||||
)
|
||||
|
||||
// readInt reads a pseudo-file (sysfs) that is known to contain an integer and
|
||||
// returns the parsed number.
|
||||
func readInt(path string) (int, error) {
|
||||
f, err := fileIOOpen(path, os.O_RDONLY)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
var b [24]byte
|
||||
n, err := f.Read(b[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
raw := b[:n]
|
||||
if len(raw) == 0 || raw[len(raw)-1] != '\n' {
|
||||
return 0, errors.New("invalid value")
|
||||
}
|
||||
return strconv.Atoi(string(raw[:len(raw)-1]))
|
||||
}
|
||||
|
||||
// driverGPIO implements periph.Driver.
|
||||
type driverGPIO struct {
|
||||
exportHandle io.Writer // handle to /sys/class/gpio/export
|
||||
}
|
||||
|
||||
func (d *driverGPIO) String() string {
|
||||
return "sysfs-gpio"
|
||||
}
|
||||
|
||||
func (d *driverGPIO) Prerequisites() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driverGPIO) After() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init initializes GPIO sysfs handling code.
|
||||
//
|
||||
// Uses gpio sysfs as described at
|
||||
// https://www.kernel.org/doc/Documentation/gpio/sysfs.txt
|
||||
//
|
||||
// GPIO sysfs is often the only way to do edge triggered interrupts. Doing this
|
||||
// requires cooperation from a driver in the kernel.
|
||||
//
|
||||
// The main drawback of GPIO sysfs is that it doesn't expose internal pull
|
||||
// resistor and it is much slower than using memory mapped hardware registers.
|
||||
func (d *driverGPIO) Init() (bool, error) {
|
||||
items, err := filepath.Glob("/sys/class/gpio/gpiochip*")
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return false, errors.New("no GPIO pin found")
|
||||
}
|
||||
|
||||
// There are hosts that use non-continuous pin numbering so use a map instead
|
||||
// of an array.
|
||||
Pins = map[int]*Pin{}
|
||||
for _, item := range items {
|
||||
if err = d.parseGPIOChip(item + "/"); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
drvGPIO.exportHandle, err = fileIOOpen("/sys/class/gpio/export", os.O_WRONLY)
|
||||
if os.IsPermission(err) {
|
||||
return true, fmt.Errorf("need more access, try as root or setup udev rules: %v", err)
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
func (d *driverGPIO) parseGPIOChip(path string) error {
|
||||
base, err := readInt(path + "base")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
number, err := readInt(path + "ngpio")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(maruel): The chip driver may lie and lists GPIO pins that cannot be
|
||||
// exported. The only way to know about it is to export it before opening.
|
||||
for i := base; i < base+number; i++ {
|
||||
if _, ok := Pins[i]; ok {
|
||||
return fmt.Errorf("found two pins with number %d", i)
|
||||
}
|
||||
p := &Pin{
|
||||
number: i,
|
||||
name: fmt.Sprintf("GPIO%d", i),
|
||||
root: fmt.Sprintf("/sys/class/gpio/gpio%d/", i),
|
||||
}
|
||||
Pins[i] = p
|
||||
if err := gpioreg.Register(p); err != nil {
|
||||
return err
|
||||
}
|
||||
// If there is a CPU memory mapped gpio pin with the same number, the
|
||||
// driver has to unregister this pin and map its own after.
|
||||
if err := gpioreg.RegisterAlias(strconv.Itoa(i), p.name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
if isLinux {
|
||||
driverreg.MustRegister(&drvGPIO)
|
||||
}
|
||||
}
|
||||
|
||||
var drvGPIO driverGPIO
|
||||
|
||||
var _ conn.Resource = &Pin{}
|
||||
var _ gpio.PinIn = &Pin{}
|
||||
var _ gpio.PinOut = &Pin{}
|
||||
var _ gpio.PinIO = &Pin{}
|
||||
var _ pin.PinFunc = &Pin{}
|
388
vendor/periph.io/x/host/v3/sysfs/i2c.go
generated
vendored
Normal file
388
vendor/periph.io/x/host/v3/sysfs/i2c.go
generated
vendored
Normal file
@ -0,0 +1,388 @@
|
||||
// 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/conn/v3/driver/driverreg"
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/gpio/gpioreg"
|
||||
"periph.io/x/conn/v3/i2c"
|
||||
"periph.io/x/conn/v3/i2c/i2creg"
|
||||
"periph.io/x/conn/v3/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/conn/v3/i2c Linux-specific implementation.
|
||||
//
|
||||
// periph.io works on many OSes!
|
||||
//
|
||||
// Instead, use https://periph.io/x/conn/v3/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 {
|
||||
driverreg.MustRegister(&drvI2C)
|
||||
}
|
||||
}
|
||||
|
||||
var drvI2C driverI2C
|
||||
|
||||
var _ i2c.Bus = &I2C{}
|
||||
var _ i2c.BusCloser = &I2C{}
|
257
vendor/periph.io/x/host/v3/sysfs/led.go
generated
vendored
Normal file
257
vendor/periph.io/x/host/v3/sysfs/led.go
generated
vendored
Normal file
@ -0,0 +1,257 @@
|
||||
// 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"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3"
|
||||
"periph.io/x/conn/v3/driver/driverreg"
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
"periph.io/x/conn/v3/pin"
|
||||
"periph.io/x/host/v3/fs"
|
||||
)
|
||||
|
||||
// LEDs is all the leds discovered on this host via sysfs.
|
||||
//
|
||||
// Depending on the user context, the LEDs may be read-only or writeable.
|
||||
var LEDs []*LED
|
||||
|
||||
// LEDByName returns a *LED for the LED name, if any.
|
||||
//
|
||||
// For all practical purpose, a LED is considered an output-only gpio.PinOut.
|
||||
func LEDByName(name string) (*LED, error) {
|
||||
// TODO(maruel): Use a bisect or a map. For now we don't expect more than a
|
||||
// handful of LEDs so it doesn't matter.
|
||||
for _, led := range LEDs {
|
||||
if led.name == name {
|
||||
if err := led.open(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return led, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("sysfs-led: invalid LED name")
|
||||
}
|
||||
|
||||
// LED represents one LED on the system.
|
||||
type LED struct {
|
||||
number int
|
||||
name string
|
||||
root string
|
||||
|
||||
mu sync.Mutex
|
||||
fBrightness *fs.File // handle to /sys/class/gpio/gpio*/direction; never closed
|
||||
}
|
||||
|
||||
// String implements conn.Resource.
|
||||
func (l *LED) String() string {
|
||||
return fmt.Sprintf("%s(%d)", l.name, l.number)
|
||||
}
|
||||
|
||||
// Halt implements conn.Resource.
|
||||
//
|
||||
// It turns the light off.
|
||||
func (l *LED) Halt() error {
|
||||
return l.Out(gpio.Low)
|
||||
}
|
||||
|
||||
// Name implements pin.Pin.
|
||||
func (l *LED) Name() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
// Number implements pin.Pin.
|
||||
func (l *LED) Number() int {
|
||||
return l.number
|
||||
}
|
||||
|
||||
// Function implements pin.Pin.
|
||||
func (l *LED) Function() string {
|
||||
return string(l.Func())
|
||||
}
|
||||
|
||||
// Func implements pin.PinFunc.
|
||||
func (l *LED) Func() pin.Func {
|
||||
if l.Read() {
|
||||
return "LED/On"
|
||||
}
|
||||
return "LED/Off"
|
||||
}
|
||||
|
||||
// SupportedFuncs implements pin.PinFunc.
|
||||
func (l *LED) SupportedFuncs() []pin.Func {
|
||||
return []pin.Func{"LED"}
|
||||
}
|
||||
|
||||
// SetFunc implements pin.PinFunc.
|
||||
func (l *LED) SetFunc(f pin.Func) error {
|
||||
return errors.New("sysfs-led: not implemented")
|
||||
}
|
||||
|
||||
// In implements gpio.PinIn.
|
||||
func (l *LED) In(pull gpio.Pull, edge gpio.Edge) error {
|
||||
if pull != gpio.Float && pull != gpio.PullNoChange {
|
||||
return errors.New("sysfs-led: pull is not supported on LED")
|
||||
}
|
||||
if edge != gpio.NoEdge {
|
||||
return errors.New("sysfs-led: edge is not supported on LED")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read implements gpio.PinIn.
|
||||
func (l *LED) Read() gpio.Level {
|
||||
err := l.open()
|
||||
if err != nil {
|
||||
return gpio.Low
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if _, err := l.fBrightness.Seek(0, 0); err != nil {
|
||||
return gpio.Low
|
||||
}
|
||||
var b [4]byte
|
||||
if _, err := l.fBrightness.Read(b[:]); err != nil {
|
||||
return gpio.Low
|
||||
}
|
||||
if b[0] != '0' {
|
||||
return gpio.High
|
||||
}
|
||||
return gpio.Low
|
||||
}
|
||||
|
||||
// WaitForEdge implements gpio.PinIn.
|
||||
func (l *LED) WaitForEdge(timeout time.Duration) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Pull implements gpio.PinIn.
|
||||
func (l *LED) Pull() gpio.Pull {
|
||||
return gpio.PullNoChange
|
||||
}
|
||||
|
||||
// DefaultPull implements gpio.PinIn.
|
||||
func (l *LED) DefaultPull() gpio.Pull {
|
||||
return gpio.PullNoChange
|
||||
}
|
||||
|
||||
// Out implements gpio.PinOut.
|
||||
func (l *LED) Out(level gpio.Level) error {
|
||||
err := l.open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if _, err = l.fBrightness.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if level {
|
||||
_, err = l.fBrightness.Write([]byte("255"))
|
||||
} else {
|
||||
_, err = l.fBrightness.Write([]byte("0"))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// PWM implements gpio.PinOut.
|
||||
//
|
||||
// This sets the intensity level, if supported. The frequency is ignored.
|
||||
func (l *LED) PWM(d gpio.Duty, f physic.Frequency) error {
|
||||
err := l.open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if _, err = l.fBrightness.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
v := (d + gpio.DutyMax/512) / (gpio.DutyMax / 256)
|
||||
_, err = l.fBrightness.Write([]byte(strconv.Itoa(int(v))))
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func (l *LED) open() error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
// trigger, max_brightness.
|
||||
var err error
|
||||
if l.fBrightness == nil {
|
||||
p := l.root + "brightness"
|
||||
if l.fBrightness, err = fs.Open(p, os.O_RDWR); err != nil {
|
||||
// Retry with read-only. This is the default setting.
|
||||
l.fBrightness, err = fs.Open(p, os.O_RDONLY)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// driverLED implements periph.Driver.
|
||||
type driverLED struct {
|
||||
}
|
||||
|
||||
func (d *driverLED) String() string {
|
||||
return "sysfs-led"
|
||||
}
|
||||
|
||||
func (d *driverLED) Prerequisites() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driverLED) After() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init initializes LEDs sysfs handling code.
|
||||
//
|
||||
// Uses led sysfs as described* at
|
||||
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-led
|
||||
//
|
||||
// * for the most minimalistic meaning of 'described'.
|
||||
func (d *driverLED) Init() (bool, error) {
|
||||
items, err := filepath.Glob("/sys/class/leds/*")
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return false, errors.New("no LED found")
|
||||
}
|
||||
// This make the LEDs in deterministic order.
|
||||
sort.Strings(items)
|
||||
for i, item := range items {
|
||||
LEDs = append(LEDs, &LED{
|
||||
number: i,
|
||||
name: filepath.Base(item),
|
||||
root: item + "/",
|
||||
})
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
if isLinux {
|
||||
driverreg.MustRegister(&drvLED)
|
||||
}
|
||||
}
|
||||
|
||||
var drvLED driverLED
|
||||
|
||||
var _ conn.Resource = &LED{}
|
||||
var _ gpio.PinIn = &LED{}
|
||||
var _ gpio.PinOut = &LED{}
|
||||
var _ gpio.PinIO = &LED{}
|
||||
var _ pin.PinFunc = &LED{}
|
622
vendor/periph.io/x/host/v3/sysfs/spi.go
generated
vendored
Normal file
622
vendor/periph.io/x/host/v3/sysfs/spi.go
generated
vendored
Normal file
@ -0,0 +1,622 @@
|
||||
// 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"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"periph.io/x/conn/v3"
|
||||
"periph.io/x/conn/v3/driver/driverreg"
|
||||
"periph.io/x/conn/v3/gpio"
|
||||
"periph.io/x/conn/v3/gpio/gpioreg"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
"periph.io/x/conn/v3/spi"
|
||||
"periph.io/x/conn/v3/spi/spireg"
|
||||
"periph.io/x/host/v3/fs"
|
||||
)
|
||||
|
||||
// NewSPI opens a SPI port via its devfs interface as described at
|
||||
// https://www.kernel.org/doc/Documentation/spi/spidev and
|
||||
// https://www.kernel.org/doc/Documentation/spi/spi-summary
|
||||
//
|
||||
// The resulting object is safe for concurrent use.
|
||||
//
|
||||
// busNumber is the bus number as exported by devfs. For example if the path is
|
||||
// /dev/spidev0.1, busNumber should be 0 and chipSelect should be 1.
|
||||
//
|
||||
// It is recommended to use https://periph.io/x/conn/v3/spi/spireg#Open
|
||||
// instead of using NewSPI() directly as the package sysfs is providing a
|
||||
// Linux-specific implementation. periph.io works on many OSes! This permits
|
||||
// it to work on all operating systems, or devices like SPI over USB.
|
||||
func NewSPI(busNumber, chipSelect int) (*SPI, error) {
|
||||
if isLinux {
|
||||
return newSPI(busNumber, chipSelect)
|
||||
}
|
||||
return nil, errors.New("sysfs-spi: not implemented on non-linux OSes")
|
||||
}
|
||||
|
||||
// SPI is an open SPI port.
|
||||
type SPI struct {
|
||||
conn spiConn
|
||||
}
|
||||
|
||||
// Close closes the handle to the SPI driver. It is not a requirement to close
|
||||
// before process termination.
|
||||
//
|
||||
// Note that the object is not reusable afterward.
|
||||
func (s *SPI) Close() error {
|
||||
s.conn.mu.Lock()
|
||||
defer s.conn.mu.Unlock()
|
||||
if err := s.conn.f.Close(); err != nil {
|
||||
return fmt.Errorf("sysfs-spi: %v", err)
|
||||
}
|
||||
s.conn.f = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SPI) String() string {
|
||||
return s.conn.String()
|
||||
}
|
||||
|
||||
// LimitSpeed implements spi.ConnCloser.
|
||||
func (s *SPI) LimitSpeed(f physic.Frequency) error {
|
||||
if f > physic.GigaHertz {
|
||||
return fmt.Errorf("sysfs-spi: invalid speed %s; maximum supported clock is 1GHz", f)
|
||||
}
|
||||
if f < 100*physic.Hertz {
|
||||
return fmt.Errorf("sysfs-spi: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f)
|
||||
}
|
||||
s.conn.mu.Lock()
|
||||
defer s.conn.mu.Unlock()
|
||||
s.conn.freqPort = f
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect implements spi.Port.
|
||||
//
|
||||
// It must be called before any I/O.
|
||||
func (s *SPI) Connect(f physic.Frequency, mode spi.Mode, bits int) (spi.Conn, error) {
|
||||
if f > physic.GigaHertz {
|
||||
return nil, fmt.Errorf("sysfs-spi: invalid speed %s; maximum supported clock is 1GHz", f)
|
||||
}
|
||||
if f < 100*physic.Hertz {
|
||||
return nil, fmt.Errorf("sysfs-spi: invalid speed %s; minimum supported clock is 100Hz; did you forget to multiply by physic.MegaHertz?", f)
|
||||
}
|
||||
if mode&^(spi.Mode3|spi.HalfDuplex|spi.NoCS|spi.LSBFirst) != 0 {
|
||||
return nil, fmt.Errorf("sysfs-spi: invalid mode %v", mode)
|
||||
}
|
||||
if bits < 1 || bits >= 256 {
|
||||
return nil, fmt.Errorf("sysfs-spi: invalid bits %d", bits)
|
||||
}
|
||||
s.conn.mu.Lock()
|
||||
defer s.conn.mu.Unlock()
|
||||
if s.conn.connected {
|
||||
return nil, errors.New("sysfs-spi: Connect() can only be called exactly once")
|
||||
}
|
||||
s.conn.connected = true
|
||||
s.conn.freqConn = f
|
||||
s.conn.bitsPerWord = uint8(bits)
|
||||
// Only mode needs to be set via an IOCTL, others can be specified in the
|
||||
// spiIOCTransfer packet, which saves a kernel call.
|
||||
m := mode & spi.Mode3
|
||||
s.conn.muPins.Lock()
|
||||
{
|
||||
if mode&spi.HalfDuplex != 0 {
|
||||
m |= threeWire
|
||||
s.conn.halfDuplex = true
|
||||
// In case initPins() had been called before Connect().
|
||||
s.conn.mosi = gpio.INVALID
|
||||
}
|
||||
if mode&spi.NoCS != 0 {
|
||||
m |= noCS
|
||||
s.conn.noCS = true
|
||||
// In case initPins() had been called before Connect().
|
||||
s.conn.cs = gpio.INVALID
|
||||
}
|
||||
}
|
||||
s.conn.muPins.Unlock()
|
||||
if mode&spi.LSBFirst != 0 {
|
||||
m |= lSBFirst
|
||||
}
|
||||
// Only the first 8 bits are used. This only works because the system is
|
||||
// running in little endian.
|
||||
if err := s.conn.setFlag(spiIOCMode, uint64(m)); err != nil {
|
||||
return nil, fmt.Errorf("sysfs-spi: setting mode %v failed: %v", mode, err)
|
||||
}
|
||||
return &s.conn, nil
|
||||
}
|
||||
|
||||
// MaxTxSize implements conn.Limits
|
||||
func (s *SPI) MaxTxSize() int {
|
||||
return drvSPI.bufSize
|
||||
}
|
||||
|
||||
// CLK implements spi.Pins.
|
||||
func (s *SPI) CLK() gpio.PinOut {
|
||||
return s.conn.CLK()
|
||||
}
|
||||
|
||||
// MISO implements spi.Pins.
|
||||
func (s *SPI) MISO() gpio.PinIn {
|
||||
return s.conn.MISO()
|
||||
}
|
||||
|
||||
// MOSI implements spi.Pins.
|
||||
func (s *SPI) MOSI() gpio.PinOut {
|
||||
return s.conn.MOSI()
|
||||
}
|
||||
|
||||
// CS implements spi.Pins.
|
||||
func (s *SPI) CS() gpio.PinOut {
|
||||
return s.conn.CS()
|
||||
}
|
||||
|
||||
// Private details.
|
||||
|
||||
func newSPI(busNumber, chipSelect int) (*SPI, error) {
|
||||
if busNumber < 0 || busNumber >= 1<<16 {
|
||||
return nil, fmt.Errorf("sysfs-spi: invalid bus %d", busNumber)
|
||||
}
|
||||
if chipSelect < 0 || chipSelect > 255 {
|
||||
return nil, fmt.Errorf("sysfs-spi: invalid chip select %d", chipSelect)
|
||||
}
|
||||
// Use the devfs path for now.
|
||||
f, err := ioctlOpen(fmt.Sprintf("/dev/spidev%d.%d", busNumber, chipSelect), os.O_RDWR)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sysfs-spi: %v", err)
|
||||
}
|
||||
return &SPI{
|
||||
spiConn{
|
||||
name: fmt.Sprintf("SPI%d.%d", busNumber, chipSelect),
|
||||
f: f,
|
||||
busNumber: busNumber,
|
||||
chipSelect: chipSelect,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// spiConn implements spi.Conn.
|
||||
type spiConn struct {
|
||||
// Immutable
|
||||
name string
|
||||
f ioctlCloser
|
||||
busNumber int
|
||||
chipSelect int
|
||||
|
||||
mu sync.Mutex
|
||||
freqPort physic.Frequency // Frequency specified at LimitSpeed()
|
||||
freqConn physic.Frequency // Frequency specified at Connect()
|
||||
bitsPerWord uint8
|
||||
connected bool
|
||||
halfDuplex bool
|
||||
noCS bool
|
||||
// Heap optimization: reduce the amount of memory allocations during
|
||||
// transactions.
|
||||
io [4]spiIOCTransfer
|
||||
p [2]spi.Packet
|
||||
|
||||
// Use a separate lock for the pins, so that they can be queried while a
|
||||
// transaction is happening.
|
||||
muPins sync.Mutex
|
||||
clk gpio.PinOut
|
||||
mosi gpio.PinOut
|
||||
miso gpio.PinIn
|
||||
cs gpio.PinOut
|
||||
}
|
||||
|
||||
func (s *spiConn) String() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// Read implements io.Reader.
|
||||
func (s *spiConn) Read(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, errors.New("sysfs-spi: Read() with empty buffer")
|
||||
}
|
||||
if drvSPI.bufSize != 0 && len(b) > drvSPI.bufSize {
|
||||
return 0, fmt.Errorf("sysfs-spi: maximum Read length is %d, got %d bytes", drvSPI.bufSize, len(b))
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.p[0].W = nil
|
||||
s.p[0].R = b
|
||||
if err := s.txPackets(s.p[:1]); err != nil {
|
||||
return 0, fmt.Errorf("sysfs-spi: Read() failed: %v", err)
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (s *spiConn) Write(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, errors.New("sysfs-spi: Write() with empty buffer")
|
||||
}
|
||||
if drvSPI.bufSize != 0 && len(b) > drvSPI.bufSize {
|
||||
return 0, fmt.Errorf("sysfs-spi: maximum Write length is %d, got %d bytes", drvSPI.bufSize, len(b))
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.p[0].W = b
|
||||
s.p[0].R = nil
|
||||
if err := s.txPackets(s.p[:1]); err != nil {
|
||||
return 0, fmt.Errorf("sysfs-spi: Write() failed: %v", err)
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Tx sends and receives data simultaneously.
|
||||
//
|
||||
// It is OK if both w and r point to the same underlying byte slice.
|
||||
//
|
||||
// spidev enforces the maximum limit of transaction size. It can be as low as
|
||||
// 4096 bytes. See the platform documentation to learn how to increase the
|
||||
// limit.
|
||||
func (s *spiConn) Tx(w, r []byte) error {
|
||||
l := len(w)
|
||||
if l == 0 {
|
||||
if l = len(r); l == 0 {
|
||||
return errors.New("sysfs-spi: Tx() with empty buffers")
|
||||
}
|
||||
} else {
|
||||
// It's not a big deal to read halfDuplex without the lock.
|
||||
if !s.halfDuplex && len(r) != 0 && len(r) != len(w) {
|
||||
return fmt.Errorf("sysfs-spi: Tx(): when both w and r are used, they must be the same size; got %d and %d bytes", len(w), len(r))
|
||||
}
|
||||
}
|
||||
if drvSPI.bufSize != 0 && l > drvSPI.bufSize {
|
||||
return fmt.Errorf("sysfs-spi: maximum Tx length is %d, got %d bytes", drvSPI.bufSize, l)
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.p[0].W = w
|
||||
s.p[0].R = r
|
||||
p := s.p[:1]
|
||||
if s.halfDuplex && len(w) != 0 && len(r) != 0 {
|
||||
// Create two packets for HalfDuplex operation: one write then one read.
|
||||
s.p[0].R = nil
|
||||
s.p[0].KeepCS = true
|
||||
s.p[1].W = nil
|
||||
s.p[1].R = r
|
||||
s.p[1].KeepCS = false
|
||||
p = s.p[:2]
|
||||
} else {
|
||||
s.p[0].KeepCS = false
|
||||
}
|
||||
if err := s.txPackets(p); err != nil {
|
||||
return fmt.Errorf("sysfs-spi: Tx() failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TxPackets sends and receives packets as specified by the user.
|
||||
//
|
||||
// spidev enforces the maximum limit of transaction size. It can be as low as
|
||||
// 4096 bytes. See the platform documentation to learn how to increase the
|
||||
// limit.
|
||||
func (s *spiConn) TxPackets(p []spi.Packet) error {
|
||||
total := 0
|
||||
for i := range p {
|
||||
lW := len(p[i].W)
|
||||
lR := len(p[i].R)
|
||||
if lW != lR && lW != 0 && lR != 0 {
|
||||
return fmt.Errorf("sysfs-spi: when both w and r are used, they must be the same size; got %d and %d bytes", lW, lR)
|
||||
}
|
||||
l := lW
|
||||
if l == 0 {
|
||||
l = lR
|
||||
}
|
||||
total += l
|
||||
}
|
||||
if total == 0 {
|
||||
return errors.New("sysfs-spi: empty packets")
|
||||
}
|
||||
if drvSPI.bufSize != 0 && total > drvSPI.bufSize {
|
||||
return fmt.Errorf("sysfs-spi: maximum TxPackets length is %d, got %d bytes", drvSPI.bufSize, total)
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.halfDuplex {
|
||||
for i := range p {
|
||||
if len(p[i].W) != 0 && len(p[i].R) != 0 {
|
||||
return errors.New("sysfs-spi: can only specify one of w or r when in half duplex")
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := s.txPackets(p); err != nil {
|
||||
return fmt.Errorf("sysfs-spi: TxPackets() failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Duplex implements conn.Conn.
|
||||
func (s *spiConn) Duplex() conn.Duplex {
|
||||
if s.halfDuplex {
|
||||
return conn.Half
|
||||
}
|
||||
return conn.Full
|
||||
}
|
||||
|
||||
// MaxTxSize implements conn.Limits.
|
||||
func (s *spiConn) MaxTxSize() int {
|
||||
return drvSPI.bufSize
|
||||
}
|
||||
|
||||
// CLK implements spi.Pins.
|
||||
func (s *spiConn) CLK() gpio.PinOut {
|
||||
s.initPins()
|
||||
return s.clk
|
||||
}
|
||||
|
||||
// MISO implements spi.Pins.
|
||||
func (s *spiConn) MISO() gpio.PinIn {
|
||||
s.initPins()
|
||||
return s.miso
|
||||
}
|
||||
|
||||
// MOSI implements spi.Pins.
|
||||
func (s *spiConn) MOSI() gpio.PinOut {
|
||||
s.initPins()
|
||||
return s.mosi
|
||||
}
|
||||
|
||||
// CS implements spi.Pins.
|
||||
func (s *spiConn) CS() gpio.PinOut {
|
||||
s.initPins()
|
||||
return s.cs
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func (s *spiConn) txPackets(p []spi.Packet) error {
|
||||
// Convert the packets.
|
||||
f := s.freqPort
|
||||
if s.freqConn != 0 && (s.freqPort == 0 || s.freqConn < s.freqPort) {
|
||||
f = s.freqConn
|
||||
}
|
||||
var m []spiIOCTransfer
|
||||
if len(p) > len(s.io) {
|
||||
m = make([]spiIOCTransfer, len(p))
|
||||
} else {
|
||||
m = s.io[:len(p)]
|
||||
}
|
||||
for i := range p {
|
||||
bits := p[i].BitsPerWord
|
||||
if bits == 0 {
|
||||
bits = s.bitsPerWord
|
||||
}
|
||||
csInvert := false
|
||||
if !s.noCS {
|
||||
// Invert CS behavior when a packet has KeepCS false, except for the last
|
||||
// packet when KeepCS is true.
|
||||
last := i == len(p)-1
|
||||
csInvert = p[i].KeepCS == last
|
||||
}
|
||||
m[i].reset(p[i].W, p[i].R, f, bits, csInvert)
|
||||
}
|
||||
return s.f.Ioctl(spiIOCTx(len(m)), uintptr(unsafe.Pointer(&m[0])))
|
||||
}
|
||||
|
||||
func (s *spiConn) setFlag(op uint, arg uint64) error {
|
||||
return s.f.Ioctl(op, uintptr(unsafe.Pointer(&arg)))
|
||||
}
|
||||
|
||||
// GetFlag allows to read back flags set via a ioctl, i.e. setFlag. It is
|
||||
// exported to allow calling it from the smoke test.
|
||||
func (s *spiConn) GetFlag(op uint) (arg uint64, err error) {
|
||||
err = s.f.Ioctl(op, uintptr(unsafe.Pointer(&arg)))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *spiConn) initPins() {
|
||||
s.muPins.Lock()
|
||||
defer s.muPins.Unlock()
|
||||
if s.clk != nil {
|
||||
return
|
||||
}
|
||||
if s.clk = gpioreg.ByName(fmt.Sprintf("SPI%d_CLK", s.busNumber)); s.clk == nil {
|
||||
s.clk = gpio.INVALID
|
||||
}
|
||||
if s.miso = gpioreg.ByName(fmt.Sprintf("SPI%d_MISO", s.busNumber)); s.miso == nil {
|
||||
s.miso = gpio.INVALID
|
||||
}
|
||||
// s.mosi is set to INVALID if HalfDuplex was specified.
|
||||
if s.mosi != gpio.INVALID {
|
||||
if s.mosi = gpioreg.ByName(fmt.Sprintf("SPI%d_MOSI", s.busNumber)); s.mosi == nil {
|
||||
s.mosi = gpio.INVALID
|
||||
}
|
||||
}
|
||||
// s.cs is set to INVALID if NoCS was specified.
|
||||
if s.cs != gpio.INVALID {
|
||||
if s.cs = gpioreg.ByName(fmt.Sprintf("SPI%d_CS%d", s.busNumber, s.chipSelect)); s.cs == nil {
|
||||
s.cs = gpio.INVALID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
cSHigh spi.Mode = 0x4 // CS active high instead of default low (not recommended)
|
||||
lSBFirst spi.Mode = 0x8 // Use little endian encoding for each word
|
||||
threeWire spi.Mode = 0x10 // half-duplex; MOSI and MISO are shared
|
||||
loop spi.Mode = 0x20 // loopback mode
|
||||
noCS spi.Mode = 0x40 // do not assert CS
|
||||
ready spi.Mode = 0x80 // slave pulls low to pause
|
||||
// The driver optionally support dual and quad data lines.
|
||||
)
|
||||
|
||||
// spidev driver IOCTL control codes.
|
||||
//
|
||||
// Constants and structure definition can be found at
|
||||
// /usr/include/linux/spi/spidev.h.
|
||||
const spiIOCMagic uint = 'k'
|
||||
|
||||
var (
|
||||
spiIOCMode = fs.IOW(spiIOCMagic, 1, 1) // SPI_IOC_WR_MODE (8 bits)
|
||||
spiIOLSBFirst = fs.IOW(spiIOCMagic, 2, 1) // SPI_IOC_WR_LSB_FIRST
|
||||
spiIOCBitsPerWord = fs.IOW(spiIOCMagic, 3, 1) // SPI_IOC_WR_BITS_PER_WORD
|
||||
spiIOCMaxSpeedHz = fs.IOW(spiIOCMagic, 4, 4) // SPI_IOC_WR_MAX_SPEED_HZ
|
||||
spiIOCMode32 = fs.IOW(spiIOCMagic, 5, 4) // SPI_IOC_WR_MODE32 (32 bits)
|
||||
)
|
||||
|
||||
// spiIOCTx(l) calculates the equivalent of SPI_IOC_MESSAGE(l) to execute a
|
||||
// transaction.
|
||||
func spiIOCTx(l int) uint {
|
||||
return fs.IOW(spiIOCMagic, 0, uint(l)*32)
|
||||
}
|
||||
|
||||
// spiIOCTransfer is spi_ioc_transfer in linux/spi/spidev.h.
|
||||
//
|
||||
// Also documented as struct spi_transfer at
|
||||
// https://www.kernel.org/doc/html/latest/driver-api/spi.html
|
||||
type spiIOCTransfer struct {
|
||||
tx uint64 // Pointer to byte slice
|
||||
rx uint64 // Pointer to byte slice
|
||||
length uint32 // buffer length of tx and rx in bytes
|
||||
speedHz uint32 // temporarily override the speed
|
||||
delayUsecs uint16 // µs to sleep before selecting the device before the next transfer
|
||||
bitsPerWord uint8 // temporarily override the number of bytes per word
|
||||
csChange uint8 // true to deassert CS before next transfer
|
||||
txNBits uint8
|
||||
rxNBits uint8
|
||||
pad uint16
|
||||
}
|
||||
|
||||
func (s *spiIOCTransfer) reset(w, r []byte, f physic.Frequency, bitsPerWord uint8, csInvert bool) {
|
||||
s.tx = 0
|
||||
s.rx = 0
|
||||
s.length = 0
|
||||
// w and r must be the same length.
|
||||
if l := len(w); l != 0 {
|
||||
s.tx = uint64(uintptr(unsafe.Pointer(&w[0])))
|
||||
s.length = uint32(l)
|
||||
}
|
||||
if l := len(r); l != 0 {
|
||||
s.rx = uint64(uintptr(unsafe.Pointer(&r[0])))
|
||||
s.length = uint32(l)
|
||||
}
|
||||
s.speedHz = uint32((f + 500*physic.MilliHertz) / physic.Hertz)
|
||||
s.delayUsecs = 0
|
||||
s.bitsPerWord = bitsPerWord
|
||||
if csInvert {
|
||||
s.csChange = 1
|
||||
} else {
|
||||
s.csChange = 0
|
||||
}
|
||||
s.txNBits = 0
|
||||
s.rxNBits = 0
|
||||
s.pad = 0
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// driverSPI implements periph.Driver.
|
||||
type driverSPI struct {
|
||||
// bufSize is the maximum number of bytes allowed per I/O on the SPI port.
|
||||
bufSize int
|
||||
}
|
||||
|
||||
func (d *driverSPI) String() string {
|
||||
return "sysfs-spi"
|
||||
}
|
||||
|
||||
func (d *driverSPI) Prerequisites() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driverSPI) After() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driverSPI) Init() (bool, error) {
|
||||
// This driver is only registered on linux, so there is no legitimate time to
|
||||
// skip it.
|
||||
|
||||
// Do not use "/sys/bus/spi/devices/spi" as Raspbian's provided udev rules
|
||||
// only modify the ACL of /dev/spidev* but not the ones in /sys/bus/...
|
||||
prefix := "/dev/spidev"
|
||||
items, err2 := filepath.Glob(prefix + "*")
|
||||
if err2 != nil {
|
||||
return true, err2
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return false, errors.New("no SPI port found")
|
||||
}
|
||||
sort.Strings(items)
|
||||
for _, item := range items {
|
||||
parts := strings.Split(item[len(prefix):], ".")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
bus, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
cs, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
name := fmt.Sprintf("/dev/spidev%d.%d", bus, cs)
|
||||
aliases := []string{fmt.Sprintf("SPI%d.%d", bus, cs)}
|
||||
n := bus
|
||||
if cs != 0 {
|
||||
n = -1
|
||||
}
|
||||
if err := spireg.Register(name, aliases, n, (&openerSPI{bus, cs}).Open); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
f, err := fs.Open("/sys/module/spidev/parameters/bufsiz", os.O_RDONLY)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
defer f.Close()
|
||||
b, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
// Update the global value.
|
||||
drvSPI.bufSize, err = strconv.Atoi(strings.TrimSpace(string(b)))
|
||||
return true, err
|
||||
}
|
||||
|
||||
type openerSPI struct {
|
||||
bus int
|
||||
cs int
|
||||
}
|
||||
|
||||
func (o *openerSPI) Open() (spi.PortCloser, error) {
|
||||
return NewSPI(o.bus, o.cs)
|
||||
}
|
||||
|
||||
func init() {
|
||||
if isLinux {
|
||||
driverreg.MustRegister(&drvSPI)
|
||||
}
|
||||
}
|
||||
|
||||
var drvSPI driverSPI
|
||||
|
||||
var _ conn.Limits = &SPI{}
|
||||
var _ conn.Limits = &spiConn{}
|
||||
var _ io.Reader = &spiConn{}
|
||||
var _ io.Writer = &spiConn{}
|
||||
var _ spi.Conn = &spiConn{}
|
||||
var _ spi.Pins = &SPI{}
|
||||
var _ spi.Pins = &spiConn{}
|
||||
var _ spi.Port = &SPI{}
|
||||
var _ spi.PortCloser = &SPI{}
|
||||
var _ fmt.Stringer = &SPI{}
|
62
vendor/periph.io/x/host/v3/sysfs/sysfs.go
generated
vendored
Normal file
62
vendor/periph.io/x/host/v3/sysfs/sysfs.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2017 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 (
|
||||
"io"
|
||||
|
||||
"periph.io/x/host/v3/fs"
|
||||
)
|
||||
|
||||
var ioctlOpen = ioctlOpenDefault
|
||||
|
||||
func ioctlOpenDefault(path string, flag int) (ioctlCloser, error) {
|
||||
f, err := fs.Open(path, flag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
var fileIOOpen = fileIOOpenDefault
|
||||
|
||||
func fileIOOpenDefault(path string, flag int) (fileIO, error) {
|
||||
f, err := fs.Open(path, flag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
type ioctlCloser interface {
|
||||
io.Closer
|
||||
fs.Ioctler
|
||||
}
|
||||
|
||||
type fileIO interface {
|
||||
Fd() uintptr
|
||||
fs.Ioctler
|
||||
io.Closer
|
||||
io.Reader
|
||||
io.Seeker
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// seekRead seeks to the beginning of a file and reads it.
|
||||
func seekRead(f fileIO, b []byte) (int, error) {
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return f.Read(b)
|
||||
}
|
||||
|
||||
// seekWrite seeks to the beginning of a file and writes to it.
|
||||
func seekWrite(f fileIO, b []byte) error {
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := f.Write(b)
|
||||
return err
|
||||
}
|
17
vendor/periph.io/x/host/v3/sysfs/sysfs_linux.go
generated
vendored
Normal file
17
vendor/periph.io/x/host/v3/sysfs/sysfs_linux.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// 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 (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const isLinux = true
|
||||
|
||||
func isErrBusy(err error) bool {
|
||||
e, ok := err.(*os.PathError)
|
||||
return ok && e.Err == syscall.EBUSY
|
||||
}
|
14
vendor/periph.io/x/host/v3/sysfs/sysfs_other.go
generated
vendored
Normal file
14
vendor/periph.io/x/host/v3/sysfs/sysfs_other.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// 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.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package sysfs
|
||||
|
||||
const isLinux = false
|
||||
|
||||
func isErrBusy(err error) bool {
|
||||
// This function is not used on non-linux.
|
||||
return false
|
||||
}
|
261
vendor/periph.io/x/host/v3/sysfs/thermal_sensor.go
generated
vendored
Normal file
261
vendor/periph.io/x/host/v3/sysfs/thermal_sensor.go
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
// 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"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"periph.io/x/conn/v3"
|
||||
"periph.io/x/conn/v3/driver/driverreg"
|
||||
"periph.io/x/conn/v3/physic"
|
||||
)
|
||||
|
||||
// ThermalSensors is all the sensors discovered on this host via sysfs. It
|
||||
// includes 'thermal' devices as well as temperature 'hwmon' devices, so
|
||||
// pre-configured onewire temperature sensors will be discovered automatically.
|
||||
var ThermalSensors []*ThermalSensor
|
||||
|
||||
// ThermalSensorByName returns a *ThermalSensor for the sensor name, if any.
|
||||
func ThermalSensorByName(name string) (*ThermalSensor, error) {
|
||||
// TODO(maruel): Use a bisect or a map. For now we don't expect more than a
|
||||
// handful of thermal sensors so it doesn't matter.
|
||||
for _, t := range ThermalSensors {
|
||||
if t.name == name {
|
||||
if err := t.open(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("sysfs-thermal: invalid sensor name")
|
||||
}
|
||||
|
||||
// ThermalSensor represents one thermal sensor on the system.
|
||||
type ThermalSensor struct {
|
||||
name string
|
||||
root string
|
||||
sensorFilename string
|
||||
typeFilename string
|
||||
|
||||
mu sync.Mutex
|
||||
nameType string
|
||||
f fileIO
|
||||
precision physic.Temperature
|
||||
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (t *ThermalSensor) String() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// Halt stops a continuous sense that was started with SenseContinuous.
|
||||
func (t *ThermalSensor) Halt() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.done != nil {
|
||||
close(t.done)
|
||||
t.done = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type of sensor as exported by sysfs.
|
||||
func (t *ThermalSensor) Type() string {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.nameType == "" {
|
||||
nameType, err := t.readType()
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
t.nameType = nameType
|
||||
}
|
||||
return t.nameType
|
||||
}
|
||||
|
||||
func (t *ThermalSensor) readType() (string, error) {
|
||||
f, err := fileIOOpen(t.root+t.typeFilename, os.O_RDONLY)
|
||||
if os.IsNotExist(err) {
|
||||
return "<unknown>", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("sysfs-thermal: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
var buf [256]byte
|
||||
n, err := f.Read(buf[:])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("sysfs-thermal: %v", err)
|
||||
}
|
||||
if n < 2 {
|
||||
return "<unknown>", nil
|
||||
}
|
||||
return string(buf[:n-1]), nil
|
||||
}
|
||||
|
||||
// Sense implements physic.SenseEnv.
|
||||
func (t *ThermalSensor) Sense(e *physic.Env) error {
|
||||
if err := t.open(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
var buf [24]byte
|
||||
n, err := seekRead(t.f, buf[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("sysfs-thermal: %v", err)
|
||||
}
|
||||
if n < 2 {
|
||||
return errors.New("sysfs-thermal: failed to read temperature")
|
||||
}
|
||||
i, err := strconv.Atoi(string(buf[:n-1]))
|
||||
if err != nil {
|
||||
return fmt.Errorf("sysfs-thermal: %v", err)
|
||||
}
|
||||
if t.precision == 0 {
|
||||
t.precision = physic.MilliKelvin
|
||||
if i < 100 {
|
||||
t.precision *= 1000
|
||||
}
|
||||
}
|
||||
e.Temperature = physic.Temperature(i)*t.precision + physic.ZeroCelsius
|
||||
return nil
|
||||
}
|
||||
|
||||
// SenseContinuous implements physic.SenseEnv.
|
||||
func (t *ThermalSensor) SenseContinuous(interval time.Duration) (<-chan physic.Env, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.done != nil {
|
||||
return nil, nil
|
||||
}
|
||||
done := make(chan struct{})
|
||||
ret := make(chan physic.Env)
|
||||
ticker := time.NewTicker(interval)
|
||||
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
close(ret)
|
||||
return
|
||||
case <-ticker.C:
|
||||
var e physic.Env
|
||||
if err := t.Sense(&e); err == nil {
|
||||
ret <- e
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
t.done = done
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Precision implements physic.SenseEnv.
|
||||
func (t *ThermalSensor) Precision(e *physic.Env) {
|
||||
if t.precision == 0 {
|
||||
dummy := physic.Env{}
|
||||
// Ignore the error.
|
||||
_ = t.Sense(&dummy)
|
||||
}
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
e.Temperature = t.precision
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func (t *ThermalSensor) open() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.f != nil {
|
||||
return nil
|
||||
}
|
||||
f, err := fileIOOpen(t.root+t.sensorFilename, os.O_RDONLY)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sysfs-thermal: %v", err)
|
||||
}
|
||||
t.f = f
|
||||
return nil
|
||||
}
|
||||
|
||||
// driverThermalSensor implements periph.Driver.
|
||||
type driverThermalSensor struct {
|
||||
}
|
||||
|
||||
func (d *driverThermalSensor) String() string {
|
||||
return "sysfs-thermal"
|
||||
}
|
||||
|
||||
func (d *driverThermalSensor) Prerequisites() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driverThermalSensor) After() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init initializes thermal sysfs handling code.
|
||||
//
|
||||
// Uses sysfs as described* at
|
||||
// https://www.kernel.org/doc/Documentation/thermal/sysfs-api.txt
|
||||
//
|
||||
// * for the most minimalistic meaning of 'described'.
|
||||
func (d *driverThermalSensor) Init() (bool, error) {
|
||||
if err := d.discoverDevices("/sys/class/thermal/*/temp", "type"); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if err := d.discoverDevices("/sys/class/hwmon/*/temp*_input", "device/name"); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if len(ThermalSensors) == 0 {
|
||||
return false, errors.New("sysfs-thermal: no sensor found")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (d *driverThermalSensor) discoverDevices(glob, typeFilename string) error {
|
||||
// This driver is only registered on linux, so there is no legitimate time to
|
||||
// skip it.
|
||||
items, err := filepath.Glob(glob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
sort.Strings(items)
|
||||
for _, item := range items {
|
||||
base := filepath.Dir(item)
|
||||
ThermalSensors = append(ThermalSensors, &ThermalSensor{
|
||||
name: filepath.Base(base),
|
||||
root: base + "/",
|
||||
sensorFilename: filepath.Base(item),
|
||||
typeFilename: typeFilename,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
if isLinux {
|
||||
driverreg.MustRegister(&drvThermalSensor)
|
||||
}
|
||||
}
|
||||
|
||||
var drvThermalSensor driverThermalSensor
|
||||
|
||||
var _ conn.Resource = &ThermalSensor{}
|
||||
var _ physic.SenseEnv = &ThermalSensor{}
|
Reference in New Issue
Block a user