// Copyright 2015, Paul Osborne // // Licensed under the Apache License, Version 2.0 or the MIT license // , 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 { //! 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, pub max_speed_hz: Option, pub lsb_first: Option, pub spi_mode: Option, } 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>(path: P) -> io::Result { 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 { 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 { self.devfile.read(buf) } } impl Write for Spidev { fn write(&mut self, buf: &[u8]) -> io::Result { 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); } }