spidev 0.7.1

Provides access to the Linux spidev interface. This interface allows for configuration of the spidev device, half-duplex SPI access, and full-duplex SPI access.
Documentation
// Copyright 2015, Paul Osborne <osbpau@gmail.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/license/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option.  This file may not be copied, modified, or distributed
// except according to those terms.

//! # Spidev
//!
//! The `spidev` crate provides access to Linux spidev devices
//! from rust.  The wrapping of the interface is pretty direct
//! and shouldn't cause any surprises.
//!
//! Additional information on the interface may be found in
//! [the kernel documentation
//! for spidev](https://www.kernel.org/doc/Documentation/spi/spidev).
//!
//! # Examples
//!
//! ```no_run
//! extern crate spidev;
//! use std::io;
//! use std::io::prelude::*;
//! use spidev::{Spidev, SpidevOptions, SpidevTransfer, SpiModeFlags};
//!
//! fn create_spi() -> io::Result<Spidev> {
//!     let mut spi = Spidev::open("/dev/spidev0.0")?;
//!     let options = SpidevOptions::new()
//!          .bits_per_word(8)
//!          .max_speed_hz(20_000)
//!          .mode(SpiModeFlags::SPI_MODE_0)
//!          .build();
//!     spi.configure(&options)?;
//!     Ok(spi)
//! }
//!
//! /// perform half duplex operations using Read and Write traits
//! fn half_duplex(spi: &mut Spidev) -> io::Result<()> {
//!     let mut rx_buf = [0_u8; 10];
//!     spi.write(&[0x01, 0x02, 0x03])?;
//!     spi.read(&mut rx_buf)?;
//!     println!("{:?}", rx_buf);
//!     Ok(())
//! }
//!
//! /// Perform full duplex operations using Ioctl
//! fn full_duplex(spi: &mut Spidev) -> io::Result<()> {
//!     // "write" transfers are also reads at the same time with
//!     // the read having the same length as the write
//!     let tx_buf = [0x01, 0x02, 0x03];
//!     let mut rx_buf = [0; 3];
//!     {
//!         let mut transfer = SpidevTransfer::read_write(&tx_buf, &mut rx_buf);
//!         spi.transfer(&mut transfer)?;
//!     }
//!     println!("{:?}", rx_buf);
//!     Ok(())
//! }
//!
//! fn main() {
//!     let mut spi = create_spi().unwrap();
//!     println!("{:?}", half_duplex(&mut spi).unwrap());
//!     println!("{:?}", full_duplex(&mut spi).unwrap());
//! }
//! ```

pub mod spidevioctl;
pub use crate::spidevioctl::SpidevTransfer;

use bitflags::bitflags;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::prelude::*;
use std::os::unix::prelude::*;
use std::path::Path;

// Constants extracted from linux/spi/spidev.h
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct SpiModeFlags: u32 {
        /// Clock Phase
        const SPI_CPHA = 0x01;
        /// Clock Polarity
        const SPI_CPOL = 0x02;
        /// Chipselect Active High?
        const SPI_CS_HIGH = 0x04;
        /// Per-word Bits On Wire
        const SPI_LSB_FIRST = 0x08;
        /// SI/SO Signals Shared
        const SPI_3WIRE = 0x10;
        /// Loopback Mode
        const SPI_LOOP = 0x20;
        /// 1 dev/bus; no chipselect
        const SPI_NO_CS = 0x40;
        /// Slave pulls low to pause
        const SPI_READY = 0x80;

        // Common Configurations
        const SPI_MODE_0 = 0x00;
        const SPI_MODE_1 = Self::SPI_CPHA.bits();
        const SPI_MODE_2 = Self::SPI_CPOL.bits();
        const SPI_MODE_3 = (Self::SPI_CPOL.bits() | Self::SPI_CPHA.bits());

        // == Only Supported with 32-bits ==

        /// Transmit with 2 wires
        const SPI_TX_DUAL = 0x100;
        /// Transmit with 4 wires
        const SPI_TX_QUAD = 0x200;
        /// Receive with 2 wires
        const SPI_RX_DUAL = 0x400;
        /// Receive with 4 wires
        const SPI_RX_QUAD = 0x800;
    }
}

/// Provide high-level access to Linux Spidev Driver
#[derive(Debug)]
pub struct Spidev {
    devfile: File,
}

/// Options that control defaults for communication on a device
///
/// Individual settings may be overridden via parameters that
/// are specified as part of any individual SpiTransfer when
/// using `transfer` or `transfer_multiple`.
///
/// Options that are not configured with one of the builder
/// functions will not be modified in the kernel when
/// `configure` is called.
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct SpidevOptions {
    pub bits_per_word: Option<u8>,
    pub max_speed_hz: Option<u32>,
    pub lsb_first: Option<bool>,
    pub spi_mode: Option<SpiModeFlags>,
}

impl SpidevOptions {
    /// Create a new, empty set of options
    pub fn new() -> SpidevOptions {
        SpidevOptions::default()
    }

    /// The number of bits in each SPI transfer word
    ///
    /// The value zero signifies eight bits.
    pub fn bits_per_word(&mut self, bits_per_word: u8) -> &mut Self {
        self.bits_per_word = Some(bits_per_word);
        self
    }

    /// The maximum SPI transfer speed, in Hz
    ///
    /// The controller can't necessarily assign that specific clock speed.
    pub fn max_speed_hz(&mut self, max_speed_hz: u32) -> &mut Self {
        self.max_speed_hz = Some(max_speed_hz);
        self
    }

    /// The bit justification used to transfer SPI words
    ///
    /// Zero indicates MSB-first; other values indicate the less common
    /// LSB-first encoding.  In both cases the specified value is
    /// right-justified in each word, so that unused (TX) or undefined (RX)
    /// bits are in the MSBs.
    pub fn lsb_first(&mut self, lsb_first: bool) -> &mut Self {
        self.lsb_first = Some(lsb_first);
        self
    }

    /// Set the SPI Transfer Mode
    ///
    /// Use the constants SPI_MODE_0..SPI_MODE_3; or if you prefer
    /// you can combine SPI_CPOL (clock polarity, idle high iff this
    /// is set) or SPI_CPHA (clock phase, sample on trailing edge
    /// iff this is set) flags.
    ///
    /// Note that this API will always prefer to use SPI_IOC_WR_MODE
    /// rathern than the 32-bit one to target the greatest number of
    /// kernels.  SPI_IOC_WR_MODE32 is only present in 3.15+ kernels.
    /// SPI_IOC_WR_MODE32 will be used iff bits higher than those in
    /// 8bits are provided (e.g. Dual/Quad Tx/Rx).
    pub fn mode(&mut self, mode: SpiModeFlags) -> &mut Self {
        self.spi_mode = Some(mode);
        self
    }

    /// Finalize and build the SpidevOptions
    pub fn build(&self) -> Self {
        *self
    }
}

impl Spidev {
    /// Wrap an already opened [`File`] for use as an spidev
    pub fn new(devfile: File) -> Self {
        Self { devfile }
    }

    /// Open the spidev device with the provided path
    ///
    /// Typically, the path will be something like `"/dev/spidev0.0"`
    /// where the first number if the bus and the second number
    /// is the chip select on that bus for the device being targeted.
    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Spidev> {
        let devfile = OpenOptions::new()
            .read(true)
            .write(true)
            .create(false)
            .open(path)?;
        Ok(Self::new(devfile))
    }

    /// Get a reference to the underlying [`File`] object
    pub fn inner(&self) -> &File {
        &self.devfile
    }

    /// Consume the object and get the underlying [`File`] object
    pub fn into_inner(self) -> File {
        self.devfile
    }

    /// Write the provided configuration to this device
    pub fn configure(&mut self, options: &SpidevOptions) -> io::Result<()> {
        // write out each present option to the device.  Options
        // that are None are left as-is, in order to reduce
        // overhead
        let fd = self.devfile.as_raw_fd();
        if let Some(bpw) = options.bits_per_word {
            spidevioctl::set_bits_per_word(fd, bpw)?;
        }
        if let Some(speed) = options.max_speed_hz {
            spidevioctl::set_max_speed_hz(fd, speed)?;
        }
        if let Some(lsb_first) = options.lsb_first {
            spidevioctl::set_lsb_first(fd, lsb_first)?;
        }
        if let Some(spi_mode_flags) = options.spi_mode {
            spidevioctl::set_mode(fd, spi_mode_flags)?;
        }
        Ok(())
    }

    /// Read the current configuration from this device
    pub fn query_configuration(&self) -> io::Result<SpidevOptions> {
        let fd = self.devfile.as_raw_fd();

        let bpw = spidevioctl::get_bits_per_word(fd)?;
        let speed = spidevioctl::get_max_speed_hz(fd)?;
        let lsb_first = (spidevioctl::get_lsb_first(fd)?) != 0;

        // Try to get the mode as 32-bit (`RD_MODE32`). Older kernels may return
        // `ENOTTY` indicating 32-bit is not supported. In that case we retry in
        // 8-bit mode.
        let mode_bits = spidevioctl::get_mode_u32(fd).or_else(|err| {
            if err.raw_os_error() == Some(libc::ENOTTY) {
                spidevioctl::get_mode(fd).map(|value| value as u32)
            } else {
                Err(err)
            }
        })?;

        let mode = SpiModeFlags::from_bits_retain(mode_bits);

        let options = SpidevOptions::new()
            .bits_per_word(bpw)
            .max_speed_hz(speed)
            .lsb_first(lsb_first)
            .mode(mode)
            .build();

        Ok(options)
    }

    /// Perform a single transfer
    pub fn transfer(&self, transfer: &mut SpidevTransfer) -> io::Result<()> {
        spidevioctl::transfer(self.devfile.as_raw_fd(), transfer)
    }

    /// Perform multiple transfers in a single system call to the kernel
    ///
    /// Chaining together multiple requests like this can reduce latency
    /// and be used for conveniently and efficient implementing some
    /// protocols without extra round trips back to userspace.
    pub fn transfer_multiple(&self, transfers: &mut [SpidevTransfer]) -> io::Result<()> {
        spidevioctl::transfer_multiple(self.devfile.as_raw_fd(), transfers)
    }
}

impl Read for Spidev {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.devfile.read(buf)
    }
}

impl Write for Spidev {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.devfile.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.devfile.flush()
    }
}

impl AsRawFd for Spidev {
    fn as_raw_fd(&self) -> RawFd {
        self.devfile.as_raw_fd()
    }
}

#[cfg(test)]
mod test {
    use super::{SpiModeFlags, SpidevOptions};

    #[test]
    fn test_spidev_options_all() {
        let options = SpidevOptions::new()
            .bits_per_word(8)
            .max_speed_hz(20_000)
            .lsb_first(false)
            .mode(SpiModeFlags::SPI_MODE_0)
            .build();
        assert_eq!(options.bits_per_word, Some(8));
        assert_eq!(options.max_speed_hz, Some(20_000));
        assert_eq!(options.lsb_first, Some(false));
        assert_eq!(options.spi_mode, Some(SpiModeFlags::SPI_MODE_0));
    }

    #[test]
    fn test_spidev_options_some() {
        let mut options = SpidevOptions::new();
        options.bits_per_word(10);
        options.lsb_first(true);
        assert_eq!(options.bits_per_word, Some(10));
        assert_eq!(options.max_speed_hz, None);
        assert_eq!(options.lsb_first, Some(true));
        assert_eq!(options.spi_mode, None);
    }
}