gpio.rsannotatedgpio.rssource111 lines · 3.1 KB · raw
1//! Cartridge GPIO port
2//!
3//! Used to communicate with any cartridge peripherals (RTC, solar sensor, gyro sensor, etc.)
4
5use crate::cartridge::rtc::{RtcWrite, SeikoRealTimeClock};
6use crate::cartridge::solar::{SolarSensor, SolarSensorWrite};
7use bincode::{Decode, Encode};
8use jgenesis_common::define_bit_enum;
9use jgenesis_common::num::GetBit;
10use std::array;
11
12define_bit_enum!(GpioPinDirection, [Input, Output]);
13define_bit_enum!(GpioMode, [WriteOnly, ReadWrite]);
14
15#[derive(Debug, Clone, Encode, Decode)]
16pub struct GpioPort {
17    pin_directions: [GpioPinDirection; 4],
18    mode: GpioMode,
19}
20
21impl GpioPort {
22    pub fn new() -> Self {
23        Self { pin_directions: [GpioPinDirection::default(); 4], mode: GpioMode::default() }
24    }
25
26    // $080000C4
27    pub fn write_data(
28        &mut self,
29        value: u16,
30        rtc: Option<&mut SeikoRealTimeClock>,
31        solar: Option<&mut SolarSensor>,
32    ) {
33        let pin = |i: u8| match self.pin_directions[i as usize] {
34            GpioPinDirection::Input => true,
35            GpioPinDirection::Output => value.bit(i),
36        };
37
38        log::trace!("GPIO data write: {:04b}", value & 0b1111);
39
40        if let Some(rtc) = rtc {
41            rtc.write(RtcWrite { chip_select: pin(2), clock: pin(0), data: pin(1) });
42        }
43
44        if let Some(solar) = solar {
45            solar.write(SolarSensorWrite { reset: pin(1), clock: pin(0) });
46        }
47    }
48
49    // $080000C4
50    pub fn read_data(
51        &self,
52        rtc: Option<&SeikoRealTimeClock>,
53        solar: Option<&SolarSensor>,
54    ) -> Option<u16> {
55        if self.mode == GpioMode::WriteOnly {
56            return None;
57        }
58
59        let data1 = match self.pin_directions[1] {
60            GpioPinDirection::Input => rtc.is_none_or(SeikoRealTimeClock::read),
61            GpioPinDirection::Output => true,
62        };
63
64        let data3 = match self.pin_directions[3] {
65            GpioPinDirection::Input => solar.is_none_or(SolarSensor::read),
66            GpioPinDirection::Output => true,
67        };
68
69        Some(0b0101 | (u16::from(data1) << 1) | (u16::from(data3) << 3))
70    }
71
72    // $080000C6
73    pub fn write_pin_directions(&mut self, value: u16) {
74        self.pin_directions = array::from_fn(|i| GpioPinDirection::from_bit(value.bit(i as u8)));
75
76        log::trace!("GPIO pin directions write ({value:04X}): {:?}", self.pin_directions);
77    }
78
79    // $080000C6
80    pub fn read_pin_directions(&self) -> Option<u16> {
81        if self.mode == GpioMode::WriteOnly {
82            return None;
83        }
84
85        let value = self
86            .pin_directions
87            .into_iter()
88            .enumerate()
89            .map(|(i, direction)| (direction as u16) << i)
90            .reduce(|a, b| a | b)
91            .unwrap();
92
93        Some(value)
94    }
95
96    // $080000C8
97    pub fn write_mode(&mut self, value: u16) {
98        self.mode = GpioMode::from_bit(value.bit(0));
99
100        log::debug!("GPIO mode write ({value:04X}): {:?}", self.mode);
101    }
102
103    // $080000C8
104    pub fn read_mode(&self) -> Option<u16> {
105        if self.mode == GpioMode::WriteOnly {
106            return None;
107        }
108
109        Some(self.mode as u16)
110    }
111}