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;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SpiModeFlags: u32 {
const SPI_CPHA = 0x01;
const SPI_CPOL = 0x02;
const SPI_CS_HIGH = 0x04;
const SPI_LSB_FIRST = 0x08;
const SPI_3WIRE = 0x10;
const SPI_LOOP = 0x20;
const SPI_NO_CS = 0x40;
const SPI_READY = 0x80;
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());
const SPI_TX_DUAL = 0x100;
const SPI_TX_QUAD = 0x200;
const SPI_RX_DUAL = 0x400;
const SPI_RX_QUAD = 0x800;
}
}
#[derive(Debug)]
pub struct Spidev {
devfile: File,
}
#[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 {
pub fn new() -> SpidevOptions {
SpidevOptions::default()
}
pub fn bits_per_word(&mut self, bits_per_word: u8) -> &mut Self {
self.bits_per_word = Some(bits_per_word);
self
}
pub fn max_speed_hz(&mut self, max_speed_hz: u32) -> &mut Self {
self.max_speed_hz = Some(max_speed_hz);
self
}
pub fn lsb_first(&mut self, lsb_first: bool) -> &mut Self {
self.lsb_first = Some(lsb_first);
self
}
pub fn mode(&mut self, mode: SpiModeFlags) -> &mut Self {
self.spi_mode = Some(mode);
self
}
pub fn build(&self) -> Self {
*self
}
}
impl Spidev {
pub fn new(devfile: File) -> Self {
Self { devfile }
}
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))
}
pub fn inner(&self) -> &File {
&self.devfile
}
pub fn into_inner(self) -> File {
self.devfile
}
pub fn configure(&mut self, options: &SpidevOptions) -> io::Result<()> {
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(())
}
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;
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)
}
pub fn transfer(&self, transfer: &mut SpidevTransfer) -> io::Result<()> {
spidevioctl::transfer(self.devfile.as_raw_fd(), transfer)
}
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);
}
}