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