sio.rsannotatedsio.rssource169 lines · 4.9 KB · raw
1//! GBA SIO / serial port
2//!
3//! SIO is largely not emulated, only stubbed out enough for games that access it to kind of work
4
5use crate::interrupts::{InterruptRegisters, InterruptType};
6use bincode::{Decode, Encode};
7use jgenesis_common::num::GetBit;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
10enum Mode {
11    #[default]
12    Normal,
13    MultiPlayer,
14    Uart,
15    JoyBus,
16    GeneralPurpose,
17}
18
19#[derive(Debug, Clone, Encode, Decode)]
20pub struct SerialPort {
21    mode: Mode,
22    rcnt: u16,
23    siocnt: u16,
24    siodata32: u32,
25    siodata8: u16,
26    next_irq_cycles: Option<u64>,
27}
28
29impl SerialPort {
30    pub fn new() -> Self {
31        Self {
32            mode: Mode::default(),
33            rcnt: 0x8000, // Sonic Advance boots into multiplayer mode if this defaults to 0
34            siocnt: 0,
35            siodata32: !0,
36            siodata8: !0,
37            next_irq_cycles: None,
38        }
39    }
40
41    pub fn read_register(&mut self, address: u32) -> u16 {
42        log::trace!("SIO read {address:08X}");
43
44        match address {
45            0x4000120 => self.siodata32 as u16,
46            0x4000122 => (self.siodata32 >> 16) as u16,
47            0x4000128 => self.read_siocnt(),
48            0x400012A => self.siodata8,
49            0x4000134 => self.read_rcnt(),
50            0x4000136 | 0x4000142 | 0x400015A => 0, // Invalid addresses that return 0
51            _ => {
52                log::debug!("Unimplemented SIO read {address:08X}");
53                !0
54            }
55        }
56    }
57
58    pub fn write_register(&mut self, address: u32, value: u16, cycles: u64) {
59        match address {
60            0x4000120 => {
61                self.siodata32 = (self.siodata32 & !0xFFFF) | u32::from(value);
62                log::trace!("SIODATA32: {:08X}", self.siodata32);
63            }
64            0x4000122 => {
65                self.siodata32 = (self.siodata32 & 0xFFFF) | (u32::from(value) << 16);
66                log::trace!("SIODATA32: {:08X}", self.siodata32);
67            }
68            0x4000128 => self.write_siocnt(value, cycles),
69            0x400012A => {
70                self.siodata8 = value;
71                log::trace!("SIODATA8: {:04X}", self.siodata8);
72            }
73            0x4000134 => self.write_rcnt(value),
74            _ => {
75                log::debug!("Unimplemented SIO write {address:08X} {value:04X}");
76            }
77        }
78    }
79
80    fn read_rcnt(&self) -> u16 {
81        self.rcnt
82    }
83
84    fn write_rcnt(&mut self, value: u16) {
85        self.rcnt = value;
86        self.update_mode();
87
88        log::trace!("RCNT write: {value:04X}");
89        log::trace!("  SIO mode: {:?}", self.mode);
90    }
91
92    fn read_siocnt(&self) -> u16 {
93        self.siocnt
94    }
95
96    fn write_siocnt(&mut self, value: u16, cycles: u64) {
97        let prev_active = self.siocnt.bit(7);
98
99        self.siocnt = value;
100        self.update_mode();
101
102        let active = self.siocnt.bit(7);
103        if self.mode == Mode::Normal && !prev_active && active {
104            log::trace!("SIO transfer started in Normal mode");
105
106            let internal_clock = self.siocnt.bit(0);
107            if internal_clock {
108                let clock_rate = if self.siocnt.bit(1) {
109                    // 2 MHz
110                    2 * 1024 * 1024
111                } else {
112                    // 256 KHz
113                    256 * 1024
114                };
115
116                let cycles_per_bit = crate::GBA_CLOCK_SPEED / clock_rate;
117                let bits = if self.siocnt.bit(12) { 32 } else { 8 };
118
119                self.next_irq_cycles = Some(cycles + bits * cycles_per_bit);
120            }
121        }
122
123        log::trace!("SIOCNT write: {value:04X}");
124        log::trace!("  SIO mode: {:?}", self.mode);
125    }
126
127    fn update_mode(&mut self) {
128        let bits = ((self.rcnt >> 14) << 2) | ((self.siocnt >> 12) & 3);
129
130        self.mode = if bits & 0b1010 == 0b0000 {
131            Mode::Normal
132        } else if bits & 0b1011 == 0b0010 {
133            Mode::MultiPlayer
134        } else if bits & 0b1011 == 0b0011 {
135            Mode::Uart
136        } else if bits & 0b1100 == 0b1000 {
137            Mode::GeneralPurpose
138        } else {
139            // bits & 0b1100 == 0b1100
140            Mode::JoyBus
141        };
142
143        if self.mode == Mode::MultiPlayer {
144            // Pretend transfer finished with no GBAs connected
145            self.siocnt &= !(1 << 7);
146            self.siodata32 = u32::from(self.siodata8) | (0xFFFF << 16);
147        }
148    }
149
150    pub fn check_for_interrupt(&mut self, cycles: u64, interrupts: &mut InterruptRegisters) {
151        let Some(next_irq_cycles) = self.next_irq_cycles else { return };
152
153        if next_irq_cycles >= cycles {
154            if self.siocnt.bit(14) {
155                interrupts.set_flag(InterruptType::Serial, next_irq_cycles);
156            }
157
158            // Act like nothing is connected
159            if self.siocnt.bit(12) {
160                self.siodata32 = !0;
161            } else {
162                self.siodata8 = !0;
163            }
164
165            self.siocnt &= !(1 << 7);
166            self.next_irq_cycles = None;
167        }
168    }
169}