Skip to main content

megadrive_sys/
ports.rs

1use core::ptr::{write_volatile, read_volatile};
2
3const IO_BASE: u32 = 0xa10000;
4const IO_DATA: *mut u8 = (IO_BASE + 3) as _;
5const IO_CTRL: *mut u8 = (IO_BASE + 9) as _;
6const IO_TX: *mut u8 = (IO_BASE + 0xf) as _;
7const IO_RX: *mut u8 = (IO_BASE + 0x11) as _;
8const IO_SCTRL: *mut u8 = (IO_BASE + 0x13) as _;
9
10fn read_reg_2(base: *mut u8, idx: u8) -> u8 {
11    unsafe {
12        read_volatile(base.offset((idx * 1) as isize))
13    }
14}
15
16fn write_reg_2(base: *mut u8, idx: u8, v: u8) {
17    unsafe {
18        write_volatile(base.offset((idx * 1) as isize), v);
19    }
20}
21
22fn read_reg_6(base: *mut u8, idx: u8) -> u8 {
23    unsafe {
24        read_volatile(base.offset((idx * 6) as isize))
25    }
26}
27
28fn write_reg_6(base: *mut u8, idx: u8, v: u8) {
29    unsafe {
30        write_volatile(base.offset((idx * 6) as isize), v);
31    }
32}
33
34/// A configurable baud rate for serial port operation.
35#[repr(u8)]
36#[derive(Clone, Copy, Debug)]
37pub enum Baud {
38    B4800 = 0b00,
39    B2400 = 0b01,
40    B1200 = 0b10,
41    B300 = 0b11,
42}
43
44/// The current status of the serial port.
45#[derive(Clone, Copy, Debug)]
46pub struct SerialStatus(u8);
47
48impl SerialStatus {
49    /// Returns true if the serial port is ready to receive.
50    pub fn is_rx_ready(self) -> bool { (self.0 & 2) != 0 }
51
52    /// Returns true if a receive error has occurred.
53    pub fn has_rx_error(self) -> bool { (self.0 & 4) != 0 }
54
55    /// Returns true if the TX buffer is full.
56    ///
57    /// No more bytes can be queued in this state.
58    pub fn is_tx_full(self) -> bool { (self.0 & 1) != 0 }
59
60    /// Returns true if this port is configured to raise an interrupt
61    /// when there is data to read.
62    pub fn rx_interrupt_enabled(self) -> bool { (self.0 & 8) != 0 }
63
64    /// Returns true if this port has serial output mode enabled.
65    pub fn serial_tx(self) -> bool { (self.0 & 0x10) != 0 }
66
67    /// Returns true if this port is configured for receiving via serial mode.
68    pub fn serial_rx(self) -> bool { (self.0 & 0x20) != 0 }
69
70    /// Returns the current configured baud rate of this serial port.
71    pub fn baud(self) -> Baud {
72        match self.0 >> 6 {
73            0b00 => Baud::B4800,
74            0b01 => Baud::B2400,
75            0b10 => Baud::B1200,
76            _ => Baud::B300,
77        }
78    }
79}
80
81/// A representation of one of the 3 IO ports on the Mega Drive.
82pub struct IOPort(u8);
83
84impl IOPort {
85    /// Set the directions of the pins on this IO port.
86    ///
87    /// A one indicates the pin is used as output.
88    pub fn set_pin_directions_raw(&self, directions: u8, enable_int: bool) {
89        let intr = if enable_int { 0x80 } else { 0 };
90        write_reg_2(IO_CTRL, self.0, intr | (directions & 0x7f));
91    }
92
93    /// Set the values of output pins.
94    ///
95    /// Any other specified pin values are ignored.
96    pub fn set_pins(&self, values: u8) {
97        write_reg_2(IO_DATA, self.0, values);
98    }
99
100    /// Get the value of all of the pins.
101    ///
102    /// Output pins show the value they were last set to.
103    pub fn get_pins(&self) -> u8 {
104        read_reg_2(IO_DATA, self.0)
105    }
106
107    /// Configure serial operation of this IO port.
108    ///
109    /// `sin` uses the serial converter for input.
110    /// `sout` uses the serial converter for output.
111    /// `rint` enables the serial interrupt for this port.
112    pub fn configure_serial(&self, sin: bool, sout: bool, rint: bool, baud: Baud) {
113        let mut v = (baud as u8) << 6;
114
115        if sin {
116            v |= 0x20;
117        }
118
119        if sout {
120            v |= 0x10;
121        }
122
123        if rint {
124            v |= 0x80;
125        }
126
127        // The 3 lowest bits are status bits.
128        write_reg_6(IO_SCTRL, self.0, v);
129    }
130
131    /// Read the serial status from the IO port.
132    pub fn serial_status_raw(&self) -> u8 {
133        read_reg_6(IO_SCTRL, self.0)
134    }
135
136    /// Read a single byte from the serial port.
137    pub fn serial_read(&self) -> u8 {
138        read_reg_6(IO_RX, self.0)
139    }
140
141    /// Write a single byte to the serial port.
142    pub fn serial_write(&self, v: u8) {
143        write_reg_6(IO_TX, self.0, v)
144    }
145}
146
147/// The first player's controller port.
148pub fn controller_1() -> IOPort { IOPort(0) }
149
150/// The second player's controller port.
151pub fn controller_2() -> IOPort { IOPort(1) }
152
153/// The optional extension port on the back of the console.
154pub fn ext() -> IOPort { IOPort(2) }