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}