spectrusty_peripherals/bus/
parallel.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! ZX Spectrum +3 CENTRONICS port bus device for parallel printers and other devices.
9use core::num::NonZeroU16;
10use core::fmt;
11use core::ops::{Deref, DerefMut};
12
13#[cfg(feature = "snapshot")]
14use serde::{Serialize, Deserialize};
15
16use spectrusty_core::{
17    bus::{BusDevice, PortAddress},
18};
19use super::ay::PassByAyAudioBusDevice;
20
21pub use crate::parallel::*;
22
23pub type Plus3CentronicsWriterBusDevice<D, W> = Plus3CentronicsBusDevice<ParallelPortWriter<
24                                                                            <D as BusDevice>::Timestamp, W>, D>;
25
26impl<P, D> fmt::Display for Plus3CentronicsBusDevice<P, D> {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        f.write_str("+3 Centronics Port")
29    }
30}
31
32bitflags! {
33    #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
34    #[cfg_attr(feature = "snapshot", serde(from = "u8", into = "u8"))]
35    #[derive(Default)]
36    struct CentronicsFlags: u8 {
37        const BUSY   = 0b0000_0001;
38        const STROBE = 0b0001_0000;
39    }
40}
41
42impl CentronicsFlags {
43    fn set_busy(&mut self, busy: bool) {
44        self.set(CentronicsFlags::BUSY, busy)
45    }
46
47    fn is_busy(self) -> bool {
48        self.intersects(CentronicsFlags::BUSY)
49    }
50
51    fn is_strobe(self) -> bool {
52        self.intersects(CentronicsFlags::STROBE)
53    }
54}
55
56/// Connects the [ParallelPortDevice] emulator as a [BusDevice] via +3 Centronics Port.
57///
58/// Substitute `P` with the type implementing [ParallelPortDevice].
59#[derive(Clone, Default, Debug)]
60#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
61pub struct Plus3CentronicsBusDevice<P, D>
62{
63    /// Provides direct access to the [ParallelPortDevice].
64    #[cfg_attr(feature = "snapshot", serde(default))]
65    pub parallel: P,
66    flags: CentronicsFlags,
67    #[cfg_attr(feature = "snapshot", serde(default))]
68    bus: D
69}
70
71#[derive(Clone, Copy, Default, Debug)]
72struct Ula3CtrlPortAddress;
73impl PortAddress for Ula3CtrlPortAddress {
74    const ADDRESS_MASK: u16 = 0b1111_0000_0000_0010;
75    const ADDRESS_BITS: u16 = 0b0001_1111_1111_1101;
76}
77
78#[derive(Clone, Copy, Default, Debug)]
79struct Ula3CentronicsPortAddress;
80impl PortAddress for Ula3CentronicsPortAddress {
81    const ADDRESS_MASK: u16 = 0b1111_0000_0000_0010;
82    const ADDRESS_BITS: u16 = 0b0000_1111_1111_1101;
83}
84
85impl<P, D> Deref for Plus3CentronicsBusDevice<P, D> {
86    type Target = P;
87    fn deref(&self) -> &Self::Target {
88        &self.parallel
89    }
90}
91
92impl<P, D> DerefMut for Plus3CentronicsBusDevice<P, D> {
93    fn deref_mut(&mut self) -> &mut Self::Target {
94        &mut self.parallel
95    }
96}
97
98impl<P, D> PassByAyAudioBusDevice for Plus3CentronicsBusDevice<P, D> {}
99
100impl<P, D> BusDevice for Plus3CentronicsBusDevice<P, D>
101    where P: ParallelPortDevice<Timestamp=D::Timestamp> + fmt::Debug,
102          D: BusDevice,
103          D::Timestamp: Copy
104{
105    type Timestamp = D::Timestamp;
106    type NextDevice = D;
107
108    #[inline]
109    fn next_device_mut(&mut self) -> &mut Self::NextDevice {
110        &mut self.bus
111    }
112
113    #[inline]
114    fn next_device_ref(&self) -> &Self::NextDevice {
115        &self.bus
116    }
117
118    #[inline]
119    fn into_next_device(self) -> Self::NextDevice {
120        self.bus
121    }
122
123    #[inline]
124    fn reset(&mut self, timestamp: Self::Timestamp) {
125        self.bus.reset(timestamp);
126    }
127
128    #[inline]
129    fn next_frame(&mut self, eof_timestamp: Self::Timestamp) {
130        self.parallel.next_frame(eof_timestamp);
131        self.bus.next_frame(eof_timestamp)
132    }
133
134    #[inline]
135    fn update_timestamp(&mut self, timestamp: Self::Timestamp) {
136        if self.flags.is_busy() {
137            self.flags.set_busy( self.parallel.poll_busy() );
138        };
139        self.bus.update_timestamp(timestamp)
140    }
141
142    #[inline]
143    fn read_io(&mut self, port: u16, timestamp: Self::Timestamp) -> Option<(u8, Option<NonZeroU16>)> {
144        if Ula3CentronicsPortAddress::match_port(port) {
145            let data = (self.flags & CentronicsFlags::BUSY).bits() | !CentronicsFlags::BUSY.bits();
146            return Some((data, None))
147        }
148        self.bus.read_io(port, timestamp)
149    }
150
151    #[inline]
152    fn write_io(&mut self, port: u16, data: u8, timestamp: Self::Timestamp) -> Option<u16> {
153        if Ula3CentronicsPortAddress::match_port(port) {
154            // println!("print: {:04x} {:b}", port, data);
155            self.parallel.write_data(data, timestamp);
156            return Some(0);
157        }
158        else if Ula3CtrlPortAddress::match_port(port) {
159            let flags = CentronicsFlags::from_bits_truncate(data);
160            let flags_diff = (self.flags ^ flags) & CentronicsFlags::STROBE;
161            if flags_diff == CentronicsFlags::STROBE {
162                self.flags ^= flags_diff;
163                self.flags.set_busy(
164                    self.parallel.write_strobe(flags.is_strobe(), timestamp));
165            }
166        }
167        self.bus.write_io(port, data, timestamp)
168    }
169}
170
171impl From<CentronicsFlags> for u8 {
172    #[inline]
173    fn from(flags: CentronicsFlags) -> u8 {
174        flags.bits()
175    }
176}
177
178impl From<u8> for CentronicsFlags {
179    #[inline]
180    fn from(flags: u8) -> CentronicsFlags {
181        CentronicsFlags::from_bits_truncate(flags)
182    }
183}