Skip to main content

ove/
spi.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! SPI bus master driver.
8//!
9//! [`Spi`] wraps an opaque `ove_spi_t` handle and exposes a method-shaped
10//! API.  An `embedded_hal::spi::SpiBus` impl is provided when the
11//! `embedded-hal` Cargo feature is enabled.
12
13use crate::bindings;
14use crate::error::{Error, Result};
15
16// SAFETY (module-wide contract for the `unsafe { bindings::ove_*(...) }` FFI
17// calls below): any handle passed to the C API is non-null and refers to a
18// live RTOS object — wrapper constructors establish validity via
19// `Error::from_code`, and `Drop` (or an explicit `deinit`) is the only place
20// a handle is released. Pointer and slice arguments reference caller-owned
21// memory valid for the duration of the call; the C side copies whatever it
22// retains and does not alias them past return (verified against the
23// signatures in `include/ove/*.h`). Blocks that deviate — `transmute`, raw
24// pointer casts from user data, slice reconstruction via `from_raw_parts`,
25// or storing a callback across the FFI boundary — carry their own
26// `// SAFETY:` comment.
27
28/// SPI clock mode (CPOL/CPHA).
29#[repr(u32)]
30#[derive(Debug, Copy, Clone, PartialEq, Eq)]
31pub enum Mode {
32    Mode0 = 0,
33    Mode1 = 1,
34    Mode2 = 2,
35    Mode3 = 3,
36}
37
38/// SPI bit order.
39#[repr(u32)]
40#[derive(Debug, Copy, Clone, PartialEq, Eq)]
41pub enum BitOrder {
42    MsbFirst = 0,
43    LsbFirst = 1,
44}
45
46/// SPI bus master.
47///
48/// Wraps an opaque `ove_spi_t` handle provided by the board configuration.
49/// Construct via [`Spi::from_handle`].
50#[derive(Debug, Copy, Clone)]
51pub struct Spi {
52    handle: bindings::ove_spi_t,
53}
54
55impl Spi {
56    /// Wrap an existing `ove_spi_t` handle.
57    ///
58    /// # Safety
59    /// `handle` must be a valid SPI handle returned by the substrate.
60    /// The caller is responsible for ensuring no other `Spi` wrapper
61    /// exists for the same handle concurrently.
62    #[inline]
63    pub const unsafe fn from_handle(handle: bindings::ove_spi_t) -> Self {
64        Self { handle }
65    }
66
67    /// Return the underlying handle.
68    #[inline]
69    pub fn raw(&self) -> bindings::ove_spi_t {
70        self.handle
71    }
72
73    /// Full-duplex SPI transfer. `tx` or `rx` may be empty for half-duplex.
74    pub fn transfer(
75        &self,
76        cs: Option<&bindings::ove_spi_cs>,
77        tx: &[u8],
78        rx: &mut [u8],
79        timeout: core::time::Duration,
80    ) -> Result<()> {
81        let len = tx.len().max(rx.len());
82        let cs_ptr = cs.map_or(core::ptr::null(), |c| c as *const _);
83        let tx_ptr = if tx.is_empty() {
84            core::ptr::null()
85        } else {
86            tx.as_ptr().cast()
87        };
88        let rx_ptr = if rx.is_empty() {
89            core::ptr::null_mut()
90        } else {
91            rx.as_mut_ptr().cast()
92        };
93        let rc = unsafe {
94            bindings::ove_spi_transfer(
95                self.handle,
96                cs_ptr,
97                tx_ptr,
98                rx_ptr,
99                len,
100                crate::time::dur_to_ns(timeout),
101            )
102        };
103        Error::from_code(rc)
104    }
105
106    /// Write-only SPI transfer.
107    pub fn write(
108        &self,
109        cs: Option<&bindings::ove_spi_cs>,
110        data: &[u8],
111        timeout: core::time::Duration,
112    ) -> Result<()> {
113        let cs_ptr = cs.map_or(core::ptr::null(), |c| c as *const _);
114        let rc = unsafe {
115            bindings::ove_spi_write(
116                self.handle,
117                cs_ptr,
118                data.as_ptr().cast(),
119                data.len(),
120                crate::time::dur_to_ns(timeout),
121            )
122        };
123        Error::from_code(rc)
124    }
125
126    /// Read-only SPI transfer (clocks out zeros).
127    pub fn read(
128        &self,
129        cs: Option<&bindings::ove_spi_cs>,
130        buf: &mut [u8],
131        timeout: core::time::Duration,
132    ) -> Result<()> {
133        let cs_ptr = cs.map_or(core::ptr::null(), |c| c as *const _);
134        let rc = unsafe {
135            bindings::ove_spi_read(
136                self.handle,
137                cs_ptr,
138                buf.as_mut_ptr().cast(),
139                buf.len(),
140                crate::time::dur_to_ns(timeout),
141            )
142        };
143        Error::from_code(rc)
144    }
145
146    /// Execute a sequence of SPI transfers under a single chip-select assertion.
147    pub fn transfer_seq(
148        &self,
149        cs: Option<&bindings::ove_spi_cs>,
150        xfers: &[bindings::ove_spi_xfer],
151        timeout: core::time::Duration,
152    ) -> Result<()> {
153        let cs_ptr = cs.map_or(core::ptr::null(), |c| c as *const _);
154        let rc = unsafe {
155            bindings::ove_spi_transfer_seq(
156                self.handle,
157                cs_ptr,
158                xfers.as_ptr(),
159                xfers.len() as u32,
160                crate::time::dur_to_ns(timeout),
161            )
162        };
163        Error::from_code(rc)
164    }
165}