blob: f4a2177bffe3a51251e576ecebc0124cf49dc45f [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.
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 load the "spidev" module to use this driver.
type Devfs struct{}
// Open Devfs /dev/spidev<bus>.<chip> and returns a connection.
func (d *Devfs) Open(bus, chip int) (driver.Conn, error) {
n := fmt.Sprintf("/dev/spidev%d.%d", bus, chip)
f, err := os.OpenFile(n, os.O_RDWR, os.ModeDevice)
if err != nil {
return nil, err
}
return &devfsConn{f: f}, nil
}
type devfsConn struct {
f *os.File
mode uint8
speed uint32
bits uint8
delay uint16
}
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.Speed:
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)
default:
return fmt.Errorf("unknown key: %v", k)
}
return nil
}
func (c *devfsConn) Transfer(tx, rx []byte) error {
if rx == nil {
rx = make([]byte, len(tx))
}
// TODO(jbd): len(tx) == len(rx)?
// TODO(jbd): Allow nil tx?
p := payload{
tx: uint64(uintptr(unsafe.Pointer(&tx[0]))),
rx: uint64(uintptr(unsafe.Pointer(&rx[0]))),
length: uint32(len(tx)),
speed: c.speed,
delay: c.delay,
bits: c.bits,
}
// 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
}