blob: 19f4bd90329e802f142e9ba8d3cc9949736ab192 [file] [log] [blame]
// Copyright 2015, Paul Osborne <[email protected]>
//
// 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);
}
}