1use pamoja_gpio::i2c::{Address, Direction};
12use pamoja_gpio::pin::{Edge, Level, Polarity};
13use pamoja_gpio::spi::Mode;
14use pamoja_gpio::GpioError;
15
16use crate::{set_last_error, PamojaStatus};
17
18pub const PAMOJA_I2C_FRAME_MAX: usize = 2;
20
21pub const PAMOJA_I2C_RESERVED_FROM: u8 = 0x78;
23
24pub const PAMOJA_I2C_RESERVED_BELOW: u8 = 0x08;
26
27const _: () = assert!(PAMOJA_I2C_RESERVED_FROM == pamoja_gpio::i2c::RESERVED_FROM);
30const _: () = assert!(PAMOJA_I2C_RESERVED_BELOW == pamoja_gpio::i2c::RESERVED_BELOW);
31
32#[repr(C)]
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub struct PamojaI2cAddress {
40 pub value: u16,
42 pub ten_bit: u8,
44}
45
46#[repr(C)]
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum PamojaI2cDirection {
50 Write = 0,
52 Read = 1,
54}
55
56#[repr(C)]
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum PamojaPinLevel {
60 Low = 0,
62 High = 1,
64}
65
66#[repr(C)]
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum PamojaPinEdge {
70 Rising = 0,
72 Falling = 1,
74 Both = 2,
76}
77
78#[repr(C)]
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub enum PamojaPinPolarity {
82 ActiveHigh = 0,
84 ActiveLow = 1,
86}
87
88#[no_mangle]
103pub unsafe extern "C" fn pamoja_i2c_address_seven_bit(
104 address: u8,
105 out_address: *mut PamojaI2cAddress,
106) -> PamojaStatus {
107 emit(Address::seven_bit(address), out_address)
108}
109
110#[no_mangle]
121pub unsafe extern "C" fn pamoja_i2c_address_ten_bit(
122 address: u16,
123 out_address: *mut PamojaI2cAddress,
124) -> PamojaStatus {
125 emit(Address::ten_bit(address), out_address)
126}
127
128#[no_mangle]
134pub extern "C" fn pamoja_i2c_address_frame_len(address: PamojaI2cAddress) -> usize {
135 borrow(address).map_or(0, Address::frame_len)
136}
137
138#[no_mangle]
152pub unsafe extern "C" fn pamoja_i2c_address_frame(
153 address: PamojaI2cAddress,
154 direction: PamojaI2cDirection,
155 out_frame: *mut u8,
156 out_frame_cap: usize,
157 out_len: *mut usize,
158) -> PamojaStatus {
159 if out_frame.is_null() || out_len.is_null() {
160 set_last_error("out_frame and out_len must not be null".to_owned());
161 return PamojaStatus::InvalidArgument;
162 }
163 let Some(address) = borrow(address) else {
164 set_last_error("address is not a valid I2C address".to_owned());
165 return PamojaStatus::InvalidArgument;
166 };
167 let out = std::slice::from_raw_parts_mut(out_frame, out_frame_cap);
168 match address.write_frame(direction.into(), out) {
169 Ok(written) => {
170 *out_len = written;
171 PamojaStatus::Ok
172 }
173 Err(error) => failed(error),
174 }
175}
176
177#[no_mangle]
185pub extern "C" fn pamoja_i2c_address_is_reserved(address: PamojaI2cAddress) -> bool {
186 borrow(address).is_some_and(Address::is_reserved)
187}
188
189#[no_mangle]
195pub extern "C" fn pamoja_i2c_address_is_general_call(address: PamojaI2cAddress) -> bool {
196 borrow(address).is_some_and(Address::is_general_call)
197}
198
199#[no_mangle]
211pub unsafe extern "C" fn pamoja_spi_mode_cpol_cpha(
212 mode: u8,
213 out_cpol: *mut bool,
214 out_cpha: *mut bool,
215) -> PamojaStatus {
216 if out_cpol.is_null() || out_cpha.is_null() {
217 set_last_error("out_cpol and out_cpha must not be null".to_owned());
218 return PamojaStatus::InvalidArgument;
219 }
220 let Some(mode) = Mode::from_number(mode) else {
221 set_last_error("SPI mode must be 0, 1, 2, or 3".to_owned());
222 return PamojaStatus::InvalidArgument;
223 };
224 let (cpol, cpha) = mode.cpol_cpha();
225 *out_cpol = cpol;
226 *out_cpha = cpha;
227 PamojaStatus::Ok
228}
229
230#[no_mangle]
236pub extern "C" fn pamoja_spi_mode_from_cpol_cpha(cpol: bool, cpha: bool) -> u8 {
237 Mode::from_cpol_cpha(cpol, cpha).number()
238}
239
240#[no_mangle]
246pub extern "C" fn pamoja_pin_level_from_bool(high: bool) -> PamojaPinLevel {
247 Level::from_bool(high).into()
248}
249
250#[no_mangle]
256pub extern "C" fn pamoja_pin_level_inverted(level: PamojaPinLevel) -> PamojaPinLevel {
257 Level::from(level).inverted().into()
258}
259
260#[no_mangle]
267pub extern "C" fn pamoja_pin_edge_triggered_by(
268 edge: PamojaPinEdge,
269 from: PamojaPinLevel,
270 to: PamojaPinLevel,
271) -> bool {
272 Edge::from(edge).triggered_by(from.into(), to.into())
273}
274
275#[no_mangle]
281pub extern "C" fn pamoja_pin_polarity_level(
282 polarity: PamojaPinPolarity,
283 asserted: bool,
284) -> PamojaPinLevel {
285 Polarity::from(polarity).level(asserted).into()
286}
287
288#[no_mangle]
294pub extern "C" fn pamoja_pin_polarity_is_asserted(
295 polarity: PamojaPinPolarity,
296 level: PamojaPinLevel,
297) -> bool {
298 Polarity::from(polarity).is_asserted(level.into())
299}
300
301impl From<PamojaI2cDirection> for Direction {
302 fn from(value: PamojaI2cDirection) -> Self {
303 match value {
304 PamojaI2cDirection::Write => Direction::Write,
305 PamojaI2cDirection::Read => Direction::Read,
306 }
307 }
308}
309
310impl From<PamojaPinLevel> for Level {
311 fn from(value: PamojaPinLevel) -> Self {
312 match value {
313 PamojaPinLevel::Low => Level::Low,
314 PamojaPinLevel::High => Level::High,
315 }
316 }
317}
318
319impl From<Level> for PamojaPinLevel {
320 fn from(value: Level) -> Self {
321 match value {
322 Level::Low => PamojaPinLevel::Low,
323 Level::High => PamojaPinLevel::High,
324 }
325 }
326}
327
328impl From<PamojaPinEdge> for Edge {
329 fn from(value: PamojaPinEdge) -> Self {
330 match value {
331 PamojaPinEdge::Rising => Edge::Rising,
332 PamojaPinEdge::Falling => Edge::Falling,
333 PamojaPinEdge::Both => Edge::Both,
334 }
335 }
336}
337
338impl From<PamojaPinPolarity> for Polarity {
339 fn from(value: PamojaPinPolarity) -> Self {
340 match value {
341 PamojaPinPolarity::ActiveHigh => Polarity::ActiveHigh,
342 PamojaPinPolarity::ActiveLow => Polarity::ActiveLow,
343 }
344 }
345}
346
347fn borrow(address: PamojaI2cAddress) -> Option<Address> {
353 if address.ten_bit == 0 {
354 u8::try_from(address.value)
355 .ok()
356 .and_then(|value| Address::seven_bit(value).ok())
357 } else {
358 Address::ten_bit(address.value).ok()
359 }
360}
361
362unsafe fn emit(
368 address: Result<Address, GpioError>,
369 out_address: *mut PamojaI2cAddress,
370) -> PamojaStatus {
371 if out_address.is_null() {
372 set_last_error("out_address must not be null".to_owned());
373 return PamojaStatus::InvalidArgument;
374 }
375 match address {
376 Ok(address) => {
377 *out_address = PamojaI2cAddress {
378 value: address.value(),
379 ten_bit: u8::from(address.is_ten_bit()),
380 };
381 PamojaStatus::Ok
382 }
383 Err(error) => failed(error),
384 }
385}
386
387fn failed(error: GpioError) -> PamojaStatus {
389 set_last_error(error.to_string());
390 PamojaStatus::InvalidArgument
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 fn seven_bit(value: u8) -> PamojaI2cAddress {
399 let mut address = PamojaI2cAddress {
400 value: 0,
401 ten_bit: 0,
402 };
403 let status = unsafe { pamoja_i2c_address_seven_bit(value, &mut address) };
405 assert_eq!(status, PamojaStatus::Ok);
406 address
407 }
408
409 #[test]
410 fn a_seven_bit_address_frames_as_one_shifted_byte() {
411 let bme = seven_bit(0x76);
412 let mut frame = [0u8; PAMOJA_I2C_FRAME_MAX];
413 let mut len = 0usize;
414
415 unsafe {
417 assert_eq!(
418 pamoja_i2c_address_frame(
419 bme,
420 PamojaI2cDirection::Write,
421 frame.as_mut_ptr(),
422 frame.len(),
423 &mut len
424 ),
425 PamojaStatus::Ok
426 );
427 assert_eq!((len, frame[0]), (1, 0xEC), "(0x76 << 1) | 0");
428 assert_eq!(
429 pamoja_i2c_address_frame(
430 bme,
431 PamojaI2cDirection::Read,
432 frame.as_mut_ptr(),
433 frame.len(),
434 &mut len
435 ),
436 PamojaStatus::Ok
437 );
438 assert_eq!((len, frame[0]), (1, 0xED), "(0x76 << 1) | 1");
439 }
440 }
441
442 #[test]
443 fn a_ten_bit_address_frames_as_the_reserved_prefix_and_the_low_byte() {
444 let mut address = PamojaI2cAddress {
445 value: 0,
446 ten_bit: 0,
447 };
448 let mut frame = [0u8; PAMOJA_I2C_FRAME_MAX];
449 let mut len = 0usize;
450
451 unsafe {
453 assert_eq!(
454 pamoja_i2c_address_ten_bit(0x2A5, &mut address),
455 PamojaStatus::Ok
456 );
457 assert_eq!(pamoja_i2c_address_frame_len(address), 2);
458 assert_eq!(
459 pamoja_i2c_address_frame(
460 address,
461 PamojaI2cDirection::Write,
462 frame.as_mut_ptr(),
463 frame.len(),
464 &mut len
465 ),
466 PamojaStatus::Ok
467 );
468 assert_eq!(len, 2);
469 assert_eq!(frame, [0xF4, 0xA5], "11110 then the top two bits, then r/w");
470 }
471 }
472
473 #[test]
474 fn an_out_of_range_address_is_refused() {
475 let mut address = PamojaI2cAddress {
476 value: 0,
477 ten_bit: 0,
478 };
479 let status = unsafe { pamoja_i2c_address_ten_bit(0x400, &mut address) };
481 assert_eq!(status, PamojaStatus::InvalidArgument);
482 }
483
484 #[test]
485 fn a_buffer_too_small_for_the_frame_is_refused() {
486 let mut address = PamojaI2cAddress {
487 value: 0,
488 ten_bit: 0,
489 };
490 let mut frame = [0u8; 1];
491 let mut len = 0usize;
492
493 unsafe {
495 assert_eq!(
496 pamoja_i2c_address_ten_bit(0x2A5, &mut address),
497 PamojaStatus::Ok
498 );
499 assert_eq!(
500 pamoja_i2c_address_frame(
501 address,
502 PamojaI2cDirection::Write,
503 frame.as_mut_ptr(),
504 frame.len(),
505 &mut len
506 ),
507 PamojaStatus::InvalidArgument
508 );
509 }
510 }
511
512 #[test]
513 fn the_reserved_ranges_are_recognised() {
514 assert!(pamoja_i2c_address_is_reserved(seven_bit(0x00)));
515 assert!(pamoja_i2c_address_is_general_call(seven_bit(0x00)));
516 assert!(pamoja_i2c_address_is_reserved(seven_bit(0x07)));
517 assert!(!pamoja_i2c_address_is_reserved(seven_bit(0x08)));
518 assert!(!pamoja_i2c_address_is_reserved(seven_bit(0x77)));
519 assert!(pamoja_i2c_address_is_reserved(seven_bit(0x78)));
520 }
521
522 #[test]
523 fn the_spi_modes_match_the_pairs_datasheets_quote() {
524 let mut cpol = false;
525 let mut cpha = false;
526 for (number, expected) in [
527 (0u8, (false, false)),
528 (1, (false, true)),
529 (2, (true, false)),
530 (3, (true, true)),
531 ] {
532 unsafe {
534 assert_eq!(
535 pamoja_spi_mode_cpol_cpha(number, &mut cpol, &mut cpha),
536 PamojaStatus::Ok
537 );
538 }
539 assert_eq!((cpol, cpha), expected, "mode {number}");
540 assert_eq!(pamoja_spi_mode_from_cpol_cpha(cpol, cpha), number);
541 }
542 }
543
544 #[test]
545 fn a_mode_number_above_three_is_refused() {
546 let mut cpol = false;
547 let mut cpha = false;
548 let status = unsafe { pamoja_spi_mode_cpol_cpha(4, &mut cpol, &mut cpha) };
550 assert_eq!(status, PamojaStatus::InvalidArgument);
551 }
552
553 #[test]
554 fn an_active_low_relay_is_energised_by_a_low_level() {
555 assert_eq!(
556 pamoja_pin_polarity_level(PamojaPinPolarity::ActiveLow, true),
557 PamojaPinLevel::Low
558 );
559 assert_eq!(
560 pamoja_pin_polarity_level(PamojaPinPolarity::ActiveHigh, true),
561 PamojaPinLevel::High
562 );
563 assert!(pamoja_pin_polarity_is_asserted(
564 PamojaPinPolarity::ActiveLow,
565 PamojaPinLevel::Low
566 ));
567 }
568
569 #[test]
570 fn an_edge_fires_only_on_its_own_transition() {
571 assert!(pamoja_pin_edge_triggered_by(
572 PamojaPinEdge::Rising,
573 PamojaPinLevel::Low,
574 PamojaPinLevel::High
575 ));
576 assert!(!pamoja_pin_edge_triggered_by(
577 PamojaPinEdge::Rising,
578 PamojaPinLevel::High,
579 PamojaPinLevel::Low
580 ));
581 assert!(pamoja_pin_edge_triggered_by(
582 PamojaPinEdge::Both,
583 PamojaPinLevel::High,
584 PamojaPinLevel::Low
585 ));
586 assert!(!pamoja_pin_edge_triggered_by(
587 PamojaPinEdge::Both,
588 PamojaPinLevel::High,
589 PamojaPinLevel::High
590 ));
591 }
592
593 #[test]
594 fn a_level_inverts() {
595 assert_eq!(
596 pamoja_pin_level_inverted(pamoja_pin_level_from_bool(true)),
597 PamojaPinLevel::Low
598 );
599 }
600}