spidev/lib.rs
1// Copyright 2015, Paul Osborne <osbpau@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/license/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! # Spidev
10//!
11//! The `spidev` crate provides access to Linux spidev devices
12//! from rust. The wrapping of the interface is pretty direct
13//! and shouldn't cause any surprises.
14//!
15//! Additional information on the interface may be found in
16//! [the kernel documentation
17//! for spidev](https://www.kernel.org/doc/Documentation/spi/spidev).
18//!
19//! # Examples
20//!
21//! ```no_run
22//! extern crate spidev;
23//! use std::io;
24//! use std::io::prelude::*;
25//! use spidev::{Spidev, SpidevOptions, SpidevTransfer, SpiModeFlags};
26//!
27//! fn create_spi() -> io::Result<Spidev> {
28//! let mut spi = Spidev::open("/dev/spidev0.0")?;
29//! let options = SpidevOptions::new()
30//! .bits_per_word(8)
31//! .max_speed_hz(20_000)
32//! .mode(SpiModeFlags::SPI_MODE_0)
33//! .build();
34//! spi.configure(&options)?;
35//! Ok(spi)
36//! }
37//!
38//! /// perform half duplex operations using Read and Write traits
39//! fn half_duplex(spi: &mut Spidev) -> io::Result<()> {
40//! let mut rx_buf = [0_u8; 10];
41//! spi.write(&[0x01, 0x02, 0x03])?;
42//! spi.read(&mut rx_buf)?;
43//! println!("{:?}", rx_buf);
44//! Ok(())
45//! }
46//!
47//! /// Perform full duplex operations using Ioctl
48//! fn full_duplex(spi: &mut Spidev) -> io::Result<()> {
49//! // "write" transfers are also reads at the same time with
50//! // the read having the same length as the write
51//! let tx_buf = [0x01, 0x02, 0x03];
52//! let mut rx_buf = [0; 3];
53//! {
54//! let mut transfer = SpidevTransfer::read_write(&tx_buf, &mut rx_buf);
55//! spi.transfer(&mut transfer)?;
56//! }
57//! println!("{:?}", rx_buf);
58//! Ok(())
59//! }
60//!
61//! fn main() {
62//! let mut spi = create_spi().unwrap();
63//! println!("{:?}", half_duplex(&mut spi).unwrap());
64//! println!("{:?}", full_duplex(&mut spi).unwrap());
65//! }
66//! ```
67
68pub mod spidevioctl;
69pub use crate::spidevioctl::SpidevTransfer;
70
71use bitflags::bitflags;
72use std::fs::{File, OpenOptions};
73use std::io;
74use std::io::prelude::*;
75use std::os::unix::prelude::*;
76use std::path::Path;
77
78// Constants extracted from linux/spi/spidev.h
79bitflags! {
80 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
81 pub struct SpiModeFlags: u32 {
82 /// Clock Phase
83 const SPI_CPHA = 0x01;
84 /// Clock Polarity
85 const SPI_CPOL = 0x02;
86 /// Chipselect Active High?
87 const SPI_CS_HIGH = 0x04;
88 /// Per-word Bits On Wire
89 const SPI_LSB_FIRST = 0x08;
90 /// SI/SO Signals Shared
91 const SPI_3WIRE = 0x10;
92 /// Loopback Mode
93 const SPI_LOOP = 0x20;
94 /// 1 dev/bus; no chipselect
95 const SPI_NO_CS = 0x40;
96 /// Slave pulls low to pause
97 const SPI_READY = 0x80;
98
99 // Common Configurations
100 const SPI_MODE_0 = 0x00;
101 const SPI_MODE_1 = Self::SPI_CPHA.bits();
102 const SPI_MODE_2 = Self::SPI_CPOL.bits();
103 const SPI_MODE_3 = (Self::SPI_CPOL.bits() | Self::SPI_CPHA.bits());
104
105 // == Only Supported with 32-bits ==
106
107 /// Transmit with 2 wires
108 const SPI_TX_DUAL = 0x100;
109 /// Transmit with 4 wires
110 const SPI_TX_QUAD = 0x200;
111 /// Receive with 2 wires
112 const SPI_RX_DUAL = 0x400;
113 /// Receive with 4 wires
114 const SPI_RX_QUAD = 0x800;
115 }
116}
117
118/// Provide high-level access to Linux Spidev Driver
119#[derive(Debug)]
120pub struct Spidev {
121 devfile: File,
122}
123
124/// Options that control defaults for communication on a device
125///
126/// Individual settings may be overridden via parameters that
127/// are specified as part of any individual SpiTransfer when
128/// using `transfer` or `transfer_multiple`.
129///
130/// Options that are not configured with one of the builder
131/// functions will not be modified in the kernel when
132/// `configure` is called.
133#[derive(Debug, Default, Clone, Copy, PartialEq)]
134pub struct SpidevOptions {
135 pub bits_per_word: Option<u8>,
136 pub max_speed_hz: Option<u32>,
137 pub lsb_first: Option<bool>,
138 pub spi_mode: Option<SpiModeFlags>,
139}
140
141impl SpidevOptions {
142 /// Create a new, empty set of options
143 pub fn new() -> SpidevOptions {
144 SpidevOptions::default()
145 }
146
147 /// The number of bits in each SPI transfer word
148 ///
149 /// The value zero signifies eight bits.
150 pub fn bits_per_word(&mut self, bits_per_word: u8) -> &mut Self {
151 self.bits_per_word = Some(bits_per_word);
152 self
153 }
154
155 /// The maximum SPI transfer speed, in Hz
156 ///
157 /// The controller can't necessarily assign that specific clock speed.
158 pub fn max_speed_hz(&mut self, max_speed_hz: u32) -> &mut Self {
159 self.max_speed_hz = Some(max_speed_hz);
160 self
161 }
162
163 /// The bit justification used to transfer SPI words
164 ///
165 /// Zero indicates MSB-first; other values indicate the less common
166 /// LSB-first encoding. In both cases the specified value is
167 /// right-justified in each word, so that unused (TX) or undefined (RX)
168 /// bits are in the MSBs.
169 pub fn lsb_first(&mut self, lsb_first: bool) -> &mut Self {
170 self.lsb_first = Some(lsb_first);
171 self
172 }
173
174 /// Set the SPI Transfer Mode
175 ///
176 /// Use the constants SPI_MODE_0..SPI_MODE_3; or if you prefer
177 /// you can combine SPI_CPOL (clock polarity, idle high iff this
178 /// is set) or SPI_CPHA (clock phase, sample on trailing edge
179 /// iff this is set) flags.
180 ///
181 /// Note that this API will always prefer to use SPI_IOC_WR_MODE
182 /// rathern than the 32-bit one to target the greatest number of
183 /// kernels. SPI_IOC_WR_MODE32 is only present in 3.15+ kernels.
184 /// SPI_IOC_WR_MODE32 will be used iff bits higher than those in
185 /// 8bits are provided (e.g. Dual/Quad Tx/Rx).
186 pub fn mode(&mut self, mode: SpiModeFlags) -> &mut Self {
187 self.spi_mode = Some(mode);
188 self
189 }
190
191 /// Finalize and build the SpidevOptions
192 pub fn build(&self) -> Self {
193 *self
194 }
195}
196
197impl Spidev {
198 /// Wrap an already opened [`File`] for use as an spidev
199 pub fn new(devfile: File) -> Self {
200 Self { devfile }
201 }
202
203 /// Open the spidev device with the provided path
204 ///
205 /// Typically, the path will be something like `"/dev/spidev0.0"`
206 /// where the first number if the bus and the second number
207 /// is the chip select on that bus for the device being targeted.
208 pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Spidev> {
209 let devfile = OpenOptions::new()
210 .read(true)
211 .write(true)
212 .create(false)
213 .open(path)?;
214 Ok(Self::new(devfile))
215 }
216
217 /// Get a reference to the underlying [`File`] object
218 pub fn inner(&self) -> &File {
219 &self.devfile
220 }
221
222 /// Consume the object and get the underlying [`File`] object
223 pub fn into_inner(self) -> File {
224 self.devfile
225 }
226
227 /// Write the provided configuration to this device
228 pub fn configure(&mut self, options: &SpidevOptions) -> io::Result<()> {
229 // write out each present option to the device. Options
230 // that are None are left as-is, in order to reduce
231 // overhead
232 let fd = self.devfile.as_raw_fd();
233 if let Some(bpw) = options.bits_per_word {
234 spidevioctl::set_bits_per_word(fd, bpw)?;
235 }
236 if let Some(speed) = options.max_speed_hz {
237 spidevioctl::set_max_speed_hz(fd, speed)?;
238 }
239 if let Some(lsb_first) = options.lsb_first {
240 spidevioctl::set_lsb_first(fd, lsb_first)?;
241 }
242 if let Some(spi_mode_flags) = options.spi_mode {
243 spidevioctl::set_mode(fd, spi_mode_flags)?;
244 }
245 Ok(())
246 }
247
248 /// Perform a single transfer
249 pub fn transfer(&self, transfer: &mut SpidevTransfer) -> io::Result<()> {
250 spidevioctl::transfer(self.devfile.as_raw_fd(), transfer)
251 }
252
253 /// Perform multiple transfers in a single system call to the kernel
254 ///
255 /// Chaining together multiple requests like this can reduce latency
256 /// and be used for conveniently and efficient implementing some
257 /// protocols without extra round trips back to userspace.
258 pub fn transfer_multiple(&self, transfers: &mut [SpidevTransfer]) -> io::Result<()> {
259 spidevioctl::transfer_multiple(self.devfile.as_raw_fd(), transfers)
260 }
261}
262
263impl Read for Spidev {
264 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
265 self.devfile.read(buf)
266 }
267}
268
269impl Write for Spidev {
270 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
271 self.devfile.write(buf)
272 }
273
274 fn flush(&mut self) -> io::Result<()> {
275 self.devfile.flush()
276 }
277}
278
279impl AsRawFd for Spidev {
280 fn as_raw_fd(&self) -> RawFd {
281 self.devfile.as_raw_fd()
282 }
283}
284
285#[cfg(test)]
286mod test {
287 use super::{SpiModeFlags, SpidevOptions};
288
289 #[test]
290 fn test_spidev_options_all() {
291 let options = SpidevOptions::new()
292 .bits_per_word(8)
293 .max_speed_hz(20_000)
294 .lsb_first(false)
295 .mode(SpiModeFlags::SPI_MODE_0)
296 .build();
297 assert_eq!(options.bits_per_word, Some(8));
298 assert_eq!(options.max_speed_hz, Some(20_000));
299 assert_eq!(options.lsb_first, Some(false));
300 assert_eq!(options.spi_mode, Some(SpiModeFlags::SPI_MODE_0));
301 }
302
303 #[test]
304 fn test_spidev_options_some() {
305 let mut options = SpidevOptions::new();
306 options.bits_per_word(10);
307 options.lsb_first(true);
308 assert_eq!(options.bits_per_word, Some(10));
309 assert_eq!(options.max_speed_hz, None);
310 assert_eq!(options.lsb_first, Some(true));
311 assert_eq!(options.spi_mode, None);
312 }
313}