blob: bc421faea27760bc77fd7f3a00c28516e5cd042c [file] [log] [blame]
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux
// +build linux
package spi
import (
"fmt"
"os"
"syscall"
"unsafe"
"golang.org/x/exp/io/spi/driver"
)
const (
devfs_MAGIC = 107
devfs_NRBITS = 8
devfs_TYPEBITS = 8
devfs_SIZEBITS = 13
devfs_DIRBITS = 3
devfs_NRSHIFT = 0
devfs_TYPESHIFT = devfs_NRSHIFT + devfs_NRBITS
devfs_SIZESHIFT = devfs_TYPESHIFT + devfs_TYPEBITS
devfs_DIRSHIFT = devfs_SIZESHIFT + devfs_SIZEBITS
devfs_READ = 2
devfs_WRITE = 4
)
type payload struct {
tx uint64
rx uint64
length uint32
speed uint32
delay uint16
bits uint8
csChange uint8
txNBits uint8
rxNBits uint8
pad uint16
}
// Devfs is an SPI driver that works against the devfs.
// You need to have loaded the "spidev" Linux module to use this driver.
type Devfs struct {
// Dev is the device to be opened.
// Device name is usually in the /dev/spidev<bus>.<chip> format.
// Required.
Dev string
// Mode is the SPI mode. SPI mode is a combination of polarity and phases.
// CPOL is the high order bit, CPHA is the low order. Pre-computed mode
// values are Mode0, Mode1, Mode2 and Mode3. The value of the mode argument
// can be overriden by the device's driver.
// Required.
Mode Mode
// MaxSpeed is the max clock speed (Hz) and can be overriden by the device's driver.
// Required.
MaxSpeed int64
}
// Open opens the provided device with the specified options
// and returns a connection.
func (d *Devfs) Open() (driver.Conn, error) {
f, err := os.OpenFile(d.Dev, os.O_RDWR, os.ModeDevice)
if err != nil {
return nil, err
}
conn := &devfsConn{f: f}
if err := conn.Configure(driver.Mode, int(d.Mode)); err != nil {
conn.Close()
return nil, err
}
if err := conn.Configure(driver.MaxSpeed, int(d.MaxSpeed)); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
type devfsConn struct {
f *os.File
mode uint8
speed uint32
bits uint8
delay uint16
csChange uint8
}
func (c *devfsConn) Configure(k, v int) error {
switch k {
case driver.Mode:
m := uint8(v)
if err := c.ioctl(requestCode(devfs_WRITE, devfs_MAGIC, 1, 1), uintptr(unsafe.Pointer(&m))); err != nil {
return fmt.Errorf("error setting mode to %v: %v", m, err)
}
c.mode = m
case driver.Bits:
b := uint8(v)
if err := c.ioctl(requestCode(devfs_WRITE, devfs_MAGIC, 3, 1), uintptr(unsafe.Pointer(&b))); err != nil {
return fmt.Errorf("error setting bits per word to %v: %v", b, err)
}
c.bits = b
case driver.MaxSpeed:
s := uint32(v)
if err := c.ioctl(requestCode(devfs_WRITE, devfs_MAGIC, 4, 4), uintptr(unsafe.Pointer(&s))); err != nil {
return fmt.Errorf("error setting speed to %v: %v", s, err)
}
c.speed = s
case driver.Order:
o := uint8(v)
if err := c.ioctl(requestCode(devfs_WRITE, devfs_MAGIC, 2, 1), uintptr(unsafe.Pointer(&o))); err != nil {
return fmt.Errorf("error setting bit order to %v: %v", o, err)
}
case driver.Delay:
c.delay = uint16(v)
case driver.CSChange:
c.csChange = uint8(v)
default:
return fmt.Errorf("unknown key: %v", k)
}
return nil
}
func (c *devfsConn) Tx(w, r []byte) error {
if r == nil {
r = make([]byte, len(w))
}
// TODO(jbd): len(w) == len(r)?
// TODO(jbd): Allow nil w.
p := payload{
tx: uint64(uintptr(unsafe.Pointer(&w[0]))),
rx: uint64(uintptr(unsafe.Pointer(&r[0]))),
length: uint32(len(w)),
speed: c.speed,
delay: c.delay,
bits: c.bits,
csChange: c.csChange,
}
// TODO(jbd): Read from the device and fill rx.
return c.ioctl(msgRequestCode(1), uintptr(unsafe.Pointer(&p)))
}
func (c *devfsConn) Close() error {
return c.f.Close()
}
// requestCode returns the device specific request code for the specified direction,
// type, number and size to be used in the ioctl call.
func requestCode(dir, typ, nr, size uintptr) uintptr {
return (dir << devfs_DIRSHIFT) | (typ << devfs_TYPESHIFT) | (nr << devfs_NRSHIFT) | (size << devfs_SIZESHIFT)
}
// msgRequestCode returns the device specific value for the SPI
// message payload to be used in the ioctl call.
// n represents the number of messages.
func msgRequestCode(n uint32) uintptr {
return uintptr(0x40006B00 + (n * 0x200000))
}
// ioctl makes an IOCTL on the open device file descriptor.
func (c *devfsConn) ioctl(a1, a2 uintptr) error {
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL, c.f.Fd(), a1, a2,
)
if errno != 0 {
return syscall.Errno(errno)
}
return nil
}