1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// 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(())
    }

    /// 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);
    }
}