1use serde::Deserialize;
8use serde_json::{Map, Value};
9
10use super::net_handle;
11use crate::wire::lenient;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum BitOrder {
16 Msb,
18 Lsb,
20}
21
22impl BitOrder {
23 pub fn as_str(&self) -> &'static str {
25 match self {
26 BitOrder::Msb => "msb",
27 BitOrder::Lsb => "lsb",
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum CsActive {
35 Low,
37 High,
39}
40
41impl CsActive {
42 pub fn as_str(&self) -> &'static str {
44 match self {
45 CsActive::Low => "low",
46 CsActive::High => "high",
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum CsMode {
54 Auto,
56 Manual,
58}
59
60impl CsMode {
61 pub fn as_str(&self) -> &'static str {
63 match self {
64 CsMode::Auto => "auto",
65 CsMode::Manual => "manual",
66 }
67 }
68}
69
70#[derive(Debug, Clone, Copy, Default)]
74pub struct SpiConfig {
75 pub mode: Option<u8>,
77 pub bit_order: Option<BitOrder>,
79 pub frequency_hz: Option<u32>,
81 pub word_size: Option<u8>,
83 pub cs_active: Option<CsActive>,
85 pub cs_mode: Option<CsMode>,
87}
88
89#[derive(Debug, Clone, Deserialize)]
91pub struct SpiEffectiveConfig {
92 #[serde(default, deserialize_with = "lenient::opt_i64")]
94 pub mode: Option<i64>,
95 #[serde(default, deserialize_with = "lenient::opt_i64")]
97 pub frequency_hz: Option<i64>,
98 #[serde(default, deserialize_with = "lenient::opt_i64")]
100 pub word_size: Option<i64>,
101 #[serde(default)]
103 pub bit_order: Option<String>,
104 #[serde(default)]
106 pub cs_active: Option<String>,
107 #[serde(default)]
109 pub cs_mode: Option<String>,
110 #[serde(flatten)]
112 pub extra: Map<String, Value>,
113}
114
115#[derive(Debug, Clone)]
118pub struct SpiTransfer {
119 pub words: Vec<u32>,
121 pub word_size: u32,
123}
124
125#[derive(Debug, Clone, Copy)]
128pub struct SpiOptions {
129 pub fill: u32,
131 pub keep_cs: bool,
133}
134
135impl Default for SpiOptions {
136 fn default() -> Self {
137 SpiOptions {
138 fill: 0xFF,
139 keep_cs: false,
140 }
141 }
142}
143
144pub(crate) mod ops {
145 use serde_json::{json, Map, Value};
146
147 use super::{SpiConfig, SpiEffectiveConfig, SpiOptions, SpiTransfer};
148 use crate::error::{Error, Result};
149 use crate::wire::{
150 as_i64, net_command, value_as, value_int_list, CommandResponse, Op, Timeout,
151 };
152
153 const ROLE: &str = "spi";
154
155 fn parse_transfer(resp: CommandResponse) -> Result<SpiTransfer> {
156 let word_size = resp
157 .extra
158 .get("word_size")
159 .and_then(as_i64)
160 .and_then(|n| u32::try_from(n).ok())
161 .ok_or_else(|| Error::Decode("SPI response missing 'word_size'".to_string()))?;
162 let words = value_int_list(resp)?;
163 Ok(SpiTransfer { words, word_size })
164 }
165
166 pub(crate) fn configure(name: &str, config: &SpiConfig) -> Op<SpiEffectiveConfig> {
167 let mut params = Map::new();
168 if let Some(m) = config.mode {
169 params.insert("mode".to_string(), json!(m));
170 }
171 if let Some(b) = config.bit_order {
172 params.insert("bit_order".to_string(), json!(b.as_str()));
173 }
174 if let Some(f) = config.frequency_hz {
175 params.insert("frequency_hz".to_string(), json!(f));
176 }
177 if let Some(w) = config.word_size {
178 params.insert("word_size".to_string(), json!(w));
179 }
180 if let Some(c) = config.cs_active {
181 params.insert("cs_active".to_string(), json!(c.as_str()));
182 }
183 if let Some(c) = config.cs_mode {
184 params.insert("cs_mode".to_string(), json!(c.as_str()));
185 }
186 Op {
187 req: net_command(name, ROLE, "config", Value::Object(params), Timeout::Default),
188 parse: value_as::<SpiEffectiveConfig>,
189 }
190 }
191
192 pub(crate) fn read(name: &str, n_words: u32, opts: &SpiOptions) -> Op<SpiTransfer> {
193 Op {
194 req: net_command(
195 name,
196 ROLE,
197 "read",
198 json!({ "n_words": n_words, "fill": opts.fill, "keep_cs": opts.keep_cs }),
199 Timeout::Default,
200 ),
201 parse: parse_transfer,
202 }
203 }
204
205 pub(crate) fn write(name: &str, data: &[u32], opts: &SpiOptions) -> Op<SpiTransfer> {
206 Op {
209 req: net_command(
210 name,
211 ROLE,
212 "write",
213 json!({ "data": data, "keep_cs": opts.keep_cs }),
214 Timeout::Default,
215 ),
216 parse: parse_transfer,
217 }
218 }
219
220 pub(crate) fn read_write(name: &str, data: &[u32], opts: &SpiOptions) -> Op<SpiTransfer> {
221 Op {
222 req: net_command(
223 name,
224 ROLE,
225 "read_write",
226 json!({ "data": data, "keep_cs": opts.keep_cs }),
227 Timeout::Default,
228 ),
229 parse: parse_transfer,
230 }
231 }
232
233 pub(crate) fn transfer(
234 name: &str,
235 data: &[u32],
236 n_words: u32,
237 opts: &SpiOptions,
238 ) -> Op<SpiTransfer> {
239 Op {
240 req: net_command(
241 name,
242 ROLE,
243 "transfer",
244 json!({
245 "data": data,
246 "n_words": n_words,
247 "fill": opts.fill,
248 "keep_cs": opts.keep_cs,
249 }),
250 Timeout::Default,
251 ),
252 parse: parse_transfer,
253 }
254 }
255}
256
257net_handle! {
258 sync: Spi,
260 async: AsyncSpi,
261 methods: {
262 fn configure(config: &SpiConfig) -> SpiEffectiveConfig = ops::configure;
265 fn read(n_words: u32, opts: &SpiOptions) -> SpiTransfer = ops::read;
267 fn write(data: &[u32], opts: &SpiOptions) -> SpiTransfer = ops::write;
270 fn read_write(data: &[u32], opts: &SpiOptions) -> SpiTransfer = ops::read_write;
272 fn transfer(data: &[u32], n_words: u32, opts: &SpiOptions) -> SpiTransfer = ops::transfer;
275 }
276}