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
// 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.
// macros import
use super::SpiModeFlags;
use nix::{ioctl_read, ioctl_write_buf, ioctl_write_ptr};
use std::io;
use std::marker::PhantomData;
use std::os::unix::prelude::*;
fn from_nix_result<T>(res: ::nix::Result<T>) -> io::Result<T> {
match res {
Ok(r) => Ok(r),
Err(err) => Err(err.into()),
}
}
/// Structure that is used when performing communication
/// with the kernel.
///
/// From the kernel documentation:
///
/// ```text
/// struct spi_ioc_transfer - describes a single SPI transfer
/// @tx_buf: Holds pointer to userspace buffer with transmit data, or null.
/// If no data is provided, zeroes are shifted out.
/// @rx_buf: Holds pointer to userspace buffer for receive data, or null.
/// @len: Length of tx and rx buffers, in bytes.
/// @speed_hz: Temporary override of the device's bitrate.
/// @bits_per_word: Temporary override of the device's wordsize.
/// @delay_usecs: If nonzero, how long to delay after the last bit transfer
/// before optionally deselecting the device before the next transfer.
/// @cs_change: True to deselect device before starting the next transfer.
///
/// This structure is mapped directly to the kernel spi_transfer structure;
/// the fields have the same meanings, except of course that the pointers
/// are in a different address space (and may be of different sizes in some
/// cases, such as 32-bit i386 userspace over a 64-bit x86_64 kernel).
/// Zero-initialize the structure, including currently unused fields, to
/// accommodate potential future updates.
///
/// SPI_IOC_MESSAGE gives userspace the equivalent of kernel spi_sync().
/// Pass it an array of related transfers, they'll execute together.
/// Each transfer may be half duplex (either direction) or full duplex.
///
/// struct spi_ioc_transfer mesg[4];
/// ...
/// status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg);
///
/// So for example one transfer might send a nine bit command (right aligned
/// in a 16-bit word), the next could read a block of 8-bit data before
/// terminating that command by temporarily deselecting the chip; the next
/// could send a different nine bit command (re-selecting the chip), and the
/// last transfer might write some register values.
/// ```
#[allow(non_camel_case_types)]
#[derive(Debug, Default)]
#[repr(C)]
pub struct spi_ioc_transfer<'a, 'b> {
tx_buf: u64,
rx_buf: u64,
len: u32,
// optional overrides
pub speed_hz: u32,
pub delay_usecs: u16,
pub bits_per_word: u8,
pub cs_change: u8,
pub pad: u32,
tx_buf_ref: PhantomData<&'a [u8]>,
rx_buf_ref: PhantomData<&'b mut [u8]>,
}
impl<'a, 'b> spi_ioc_transfer<'a, 'b> {
/// Create a read transfer
pub fn read(buff: &'b mut [u8]) -> Self {
spi_ioc_transfer {
rx_buf: buff.as_ptr() as *const () as usize as u64,
len: buff.len() as u32,
..Default::default()
}
}
/// Create a write transfer
pub fn write(buff: &'a [u8]) -> Self {
spi_ioc_transfer {
tx_buf: buff.as_ptr() as *const () as usize as u64,
len: buff.len() as u32,
..Default::default()
}
}
/// Create a read/write transfer.
/// Note that the `tx_buf` and `rx_buf` must be the same length.
pub fn read_write(tx_buf: &'a [u8], rx_buf: &'b mut [u8]) -> Self {
assert_eq!(tx_buf.len(), rx_buf.len());
spi_ioc_transfer {
rx_buf: rx_buf.as_ptr() as *const () as usize as u64,
tx_buf: tx_buf.as_ptr() as *const () as usize as u64,
len: tx_buf.len() as u32,
..Default::default()
}
}
/// Create a delay transfer of a number of microseconds
pub fn delay(microseconds: u16) -> Self {
spi_ioc_transfer {
delay_usecs: microseconds,
len: 0,
..Default::default()
}
}
}
mod ioctl {
use super::*;
const SPI_IOC_MAGIC: u8 = b'k';
const SPI_IOC_NR_TRANSFER: u8 = 0;
const SPI_IOC_NR_MODE: u8 = 1;
const SPI_IOC_NR_LSB_FIRST: u8 = 2;
const SPI_IOC_NR_BITS_PER_WORD: u8 = 3;
const SPI_IOC_NR_MAX_SPEED_HZ: u8 = 4;
const SPI_IOC_NR_MODE32: u8 = 5;
ioctl_read!(get_mode_u8, SPI_IOC_MAGIC, SPI_IOC_NR_MODE, u8);
ioctl_read!(get_mode_u32, SPI_IOC_MAGIC, SPI_IOC_NR_MODE32, u32);
ioctl_write_ptr!(set_mode, SPI_IOC_MAGIC, SPI_IOC_NR_MODE, u8);
ioctl_write_ptr!(set_mode32, SPI_IOC_MAGIC, SPI_IOC_NR_MODE32, u32);
ioctl_read!(get_lsb_first, SPI_IOC_MAGIC, SPI_IOC_NR_LSB_FIRST, u8);
ioctl_write_ptr!(set_lsb_first, SPI_IOC_MAGIC, SPI_IOC_NR_LSB_FIRST, u8);
ioctl_read!(
get_bits_per_word,
SPI_IOC_MAGIC,
SPI_IOC_NR_BITS_PER_WORD,
u8
);
ioctl_write_ptr!(
set_bits_per_word,
SPI_IOC_MAGIC,
SPI_IOC_NR_BITS_PER_WORD,
u8
);
ioctl_read!(
get_max_speed_hz,
SPI_IOC_MAGIC,
SPI_IOC_NR_MAX_SPEED_HZ,
u32
);
ioctl_write_ptr!(
set_max_speed_hz,
SPI_IOC_MAGIC,
SPI_IOC_NR_MAX_SPEED_HZ,
u32
);
// NOTE: this macro works for single transfers but cannot properly
// calculate size for multi transfer whose length we will not know
// until runtime. We fallback to using the underlying ioctl for that
// use case.
ioctl_write_ptr!(
spidev_transfer,
SPI_IOC_MAGIC,
SPI_IOC_NR_TRANSFER,
spi_ioc_transfer
);
ioctl_write_buf!(
spidev_transfer_buf,
SPI_IOC_MAGIC,
SPI_IOC_NR_TRANSFER,
spi_ioc_transfer
);
}
/// Representation of a spidev transfer that is shared
/// with external users
pub type SpidevTransfer<'a, 'b> = spi_ioc_transfer<'a, 'b>;
pub fn get_mode(fd: RawFd) -> io::Result<u8> {
let mut mode: u8 = 0;
from_nix_result(unsafe { ioctl::get_mode_u8(fd, &mut mode) })?;
Ok(mode)
}
pub fn set_mode(fd: RawFd, mode: SpiModeFlags) -> io::Result<()> {
// we will always use the 8-bit mode write unless bits not in
// the 8-bit mask are used. This is because WR_MODE32 was not
// added until later kernels. This provides a reasonable story
// for forwards and backwards compatibility
if (mode.bits() & 0xFFFFFF00) != 0 {
from_nix_result(unsafe { ioctl::set_mode32(fd, &mode.bits()) })?;
} else {
let bits: u8 = mode.bits() as u8;
from_nix_result(unsafe { ioctl::set_mode(fd, &bits) })?;
}
Ok(())
}
pub fn get_lsb_first(fd: RawFd) -> io::Result<u8> {
let mut lsb_first: u8 = 0;
from_nix_result(unsafe { ioctl::get_lsb_first(fd, &mut lsb_first) })?;
Ok(lsb_first)
}
pub fn set_lsb_first(fd: RawFd, lsb_first: bool) -> io::Result<()> {
let lsb_first_value: u8 = if lsb_first { 1 } else { 0 };
from_nix_result(unsafe { ioctl::set_lsb_first(fd, &lsb_first_value) })?;
Ok(())
}
pub fn get_bits_per_word(fd: RawFd) -> io::Result<u8> {
let mut bits_per_word: u8 = 0;
from_nix_result(unsafe { ioctl::get_bits_per_word(fd, &mut bits_per_word) })?;
Ok(bits_per_word)
}
pub fn set_bits_per_word(fd: RawFd, bits_per_word: u8) -> io::Result<()> {
from_nix_result(unsafe { ioctl::set_bits_per_word(fd, &bits_per_word) })?;
Ok(())
}
pub fn get_max_speed_hz(fd: RawFd) -> io::Result<u32> {
let mut max_speed_hz: u32 = 0;
from_nix_result(unsafe { ioctl::get_max_speed_hz(fd, &mut max_speed_hz) })?;
Ok(max_speed_hz)
}
pub fn set_max_speed_hz(fd: RawFd, max_speed_hz: u32) -> io::Result<()> {
from_nix_result(unsafe { ioctl::set_max_speed_hz(fd, &max_speed_hz) })?;
Ok(())
}
pub fn transfer(fd: RawFd, transfer: &mut SpidevTransfer) -> io::Result<()> {
// The kernel will directly modify the rx_buf of the SpidevTransfer
// rx_buf if present, so there is no need to do any additional work
from_nix_result(unsafe { ioctl::spidev_transfer(fd, transfer) })?;
Ok(())
}
pub fn transfer_multiple(fd: RawFd, transfers: &mut [SpidevTransfer]) -> io::Result<()> {
from_nix_result(unsafe { ioctl::spidev_transfer_buf(fd, transfers) })?;
Ok(())
}