1 // license:GPL-2.0+
2 // copyright-holders:Dirk Best, Olivier Galibert
3 /***************************************************************************
4
5 VTech Laser/VZ Floppy Controller Cartridge
6
7 Laser DD 20
8 Dick Smith Electronics X-7304
9
10 ***************************************************************************/
11
12 #include "emu.h"
13 #include "floppy.h"
14
15
16 //**************************************************************************
17 // DEVICE DEFINITIONS
18 //**************************************************************************
19
20 DEFINE_DEVICE_TYPE(VTECH_FLOPPY_CONTROLLER, vtech_floppy_controller_device, "vtech_fdc", "Laser/VZ Floppy Disk Controller")
21
map(address_map & map)22 void vtech_floppy_controller_device::map(address_map &map)
23 {
24 map(0, 0).w(FUNC(vtech_floppy_controller_device::latch_w));
25 map(1, 1).r(FUNC(vtech_floppy_controller_device::shifter_r));
26 map(2, 2).r(FUNC(vtech_floppy_controller_device::rd_r));
27 map(3, 3).r(FUNC(vtech_floppy_controller_device::wpt_r));
28 }
29
30 //-------------------------------------------------
31 // rom_region - device-specific ROM region
32 //-------------------------------------------------
33
34 ROM_START( floppy )
35 ROM_REGION(0x3000, "software", 0)
CRC(b6ed6084)36 ROM_LOAD("vzdos.rom", 0x0000, 0x2000, CRC(b6ed6084) SHA1(59d1cbcfa6c5e1906a32704fbf0d9670f0d1fd8b))
37 ROM_END
38
39 const tiny_rom_entry *vtech_floppy_controller_device::device_rom_region() const
40 {
41 return ROM_NAME( floppy );
42 }
43
44 //-------------------------------------------------
45 // device_add_mconfig - add device configuration
46 //-------------------------------------------------
47
laser_floppies(device_slot_interface & device)48 static void laser_floppies(device_slot_interface &device)
49 {
50 device.option_add("525", FLOPPY_525_SSSD);
51 }
52
device_add_mconfig(machine_config & config)53 void vtech_floppy_controller_device::device_add_mconfig(machine_config &config)
54 {
55 VTECH_MEMEXP_SLOT(config, m_memexp);
56 FLOPPY_CONNECTOR(config, m_floppy0, laser_floppies, "525", floppy_image_device::default_floppy_formats);
57 FLOPPY_CONNECTOR(config, m_floppy1, laser_floppies, "525", floppy_image_device::default_floppy_formats);
58 }
59
60
61 //**************************************************************************
62 // LIVE DEVICE
63 //**************************************************************************
64
65 //-------------------------------------------------
66 // vtech_floppy_controller_device - constructor
67 //-------------------------------------------------
68
vtech_floppy_controller_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)69 vtech_floppy_controller_device::vtech_floppy_controller_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
70 device_t(mconfig, VTECH_FLOPPY_CONTROLLER, tag, owner, clock),
71 device_vtech_memexp_interface(mconfig, *this),
72 m_memexp(*this, "mem"),
73 m_floppy0(*this, "0"),
74 m_floppy1(*this, "1"),
75 m_floppy(nullptr), m_latch(0), m_shifter(0), m_latching_inverter(false), m_current_cyl(0), m_write_position(0)
76 {
77 }
78
79 //-------------------------------------------------
80 // device_start - device-specific startup
81 //-------------------------------------------------
82
device_start()83 void vtech_floppy_controller_device::device_start()
84 {
85 save_item(NAME(m_latch));
86 save_item(NAME(m_shifter));
87 save_item(NAME(m_latching_inverter));
88 save_item(NAME(m_current_cyl));
89 save_item(NAME(m_last_latching_inverter_update_time));
90 save_item(NAME(m_write_start_time));
91 save_item(NAME(m_write_position));
92
93 // TODO: save m_write_buffer and rebuild m_floppy after load
94
95 uint8_t *bios = memregion("software")->base();
96
97 // Obvious bugs... must have worked by sheer luck and very subtle
98 // timings. Our current z80 is not subtle enough.
99
100 bios[0x1678] = 0x75;
101 bios[0x1688] = 0x85;
102 }
103
104 //-------------------------------------------------
105 // device_reset - device-specific reset
106 //-------------------------------------------------
107
device_reset()108 void vtech_floppy_controller_device::device_reset()
109 {
110 program_space().install_rom(0x4000, 0x5fff, memregion("software")->base());
111
112 io_space().install_device(0x10, 0x1f, *this, &vtech_floppy_controller_device::map);
113
114 m_latch = 0x00;
115 m_floppy = nullptr;
116 m_current_cyl = 0;
117 m_shifter = 0x00;
118 m_latching_inverter = false;
119 m_last_latching_inverter_update_time = machine().time();
120 m_write_start_time = attotime::never;
121 m_write_position = 0;
122 memset(m_write_buffer, 0, sizeof(m_write_buffer));
123 }
124
125
126 //**************************************************************************
127 // IMPLEMENTATION
128 //**************************************************************************
129
130 // latch at +0 is linked to:
131 // bits 0-3: track step motor phases
132 // bit 5: write data (flux reversal on every level change)
133 // bit 6: !write request
134 // bits 4,7: floppy select
135
latch_w(uint8_t data)136 void vtech_floppy_controller_device::latch_w(uint8_t data)
137 {
138 uint8_t diff = m_latch ^ data;
139 m_latch = data;
140
141 floppy_image_device *newflop = nullptr;
142 if(m_latch & 0x10)
143 newflop = m_floppy0->get_device();
144 else if(m_latch & 0x80)
145 newflop = m_floppy1->get_device();
146
147 if(newflop != m_floppy) {
148 update_latching_inverter();
149 flush_writes();
150 if(m_floppy) {
151 m_floppy->mon_w(1);
152 m_floppy->setup_index_pulse_cb(floppy_image_device::index_pulse_cb());
153 }
154 if(newflop) {
155 newflop->set_rpm(85);
156 newflop->mon_w(0);
157 newflop->setup_index_pulse_cb(floppy_image_device::index_pulse_cb(&vtech_floppy_controller_device::index_callback, this));
158 m_current_cyl = newflop->get_cyl() << 1;
159 }
160 m_floppy = newflop;
161 }
162
163 if(m_floppy) {
164 int cph = m_current_cyl & 3;
165 int pcyl = m_current_cyl;
166 if(!(m_latch & (1 << cph))) {
167 if(m_current_cyl < 84*2 && (m_latch & (1 << ((cph+1) & 3))))
168 m_current_cyl++;
169 if(m_current_cyl && (m_latch & (1 << ((cph+3) & 3))))
170 m_current_cyl--;
171 if(m_current_cyl != pcyl && !(m_current_cyl & 1)) {
172 m_floppy->dir_w(m_current_cyl < pcyl);
173 m_floppy->stp_w(true);
174 m_floppy->stp_w(false);
175 m_floppy->stp_w(true);
176 }
177 }
178 }
179
180 if(diff & 0x40) {
181 if(!(m_latch & 0x40)) {
182 m_write_start_time = machine().time();
183 m_write_position = 0;
184 if(m_floppy)
185 m_floppy->set_write_splice(m_write_start_time);
186
187 } else {
188 update_latching_inverter();
189 flush_writes();
190 m_write_start_time = attotime::never;
191 }
192 }
193 if(!(m_latch & 0x40) && (diff & 0x20)) {
194 if(m_write_position == ARRAY_LENGTH(m_write_buffer)) {
195 update_latching_inverter();
196 flush_writes(true);
197 }
198 m_write_buffer[m_write_position++] = machine().time();
199 }
200 }
201
202
203 // The read data line is connected to a flip/flop with inverted input
204 // connected to the input. That means it inverts its value on every
205 // floppy flux reversal. We'll call it a latching inverter.
206 //
207 // The latching inverter is connected to a 8-bits shift register. On
208 // reading the shifter address we get:
209 // - the inverted inverter output is shifted through the lsb of the shift register
210 // - the inverter is cleared
211
shifter_r()212 uint8_t vtech_floppy_controller_device::shifter_r()
213 {
214 if (!machine().side_effects_disabled())
215 {
216 update_latching_inverter();
217 m_shifter = (m_shifter << 1) | !m_latching_inverter;
218 m_latching_inverter = false;
219 }
220 return m_shifter;
221 }
222
223
224 // Linked to the latching inverter on bit 7, rest is floating
rd_r()225 uint8_t vtech_floppy_controller_device::rd_r()
226 {
227 update_latching_inverter();
228 return m_latching_inverter ? 0x80 : 0x00;
229 }
230
231
232 // Linked to wp signal on bit 7, rest is floating
wpt_r()233 uint8_t vtech_floppy_controller_device::wpt_r()
234 {
235 return m_floppy && m_floppy->wpt_r() ? 0x80 : 0x00;
236 }
237
update_latching_inverter()238 void vtech_floppy_controller_device::update_latching_inverter()
239 {
240 attotime now = machine().time();
241 if(!m_floppy) {
242 m_last_latching_inverter_update_time = now;
243 return;
244 }
245
246 attotime when = m_last_latching_inverter_update_time;
247 for(;;) {
248 when = m_floppy->get_next_transition(when);
249 if(when == attotime::never || when > now)
250 break;
251 m_latching_inverter = !m_latching_inverter;
252 }
253 m_last_latching_inverter_update_time = now;
254 }
255
index_callback(floppy_image_device * floppy,int state)256 void vtech_floppy_controller_device::index_callback(floppy_image_device *floppy, int state)
257 {
258 update_latching_inverter();
259 flush_writes(true);
260 }
261
flush_writes(bool keep_margin)262 void vtech_floppy_controller_device::flush_writes(bool keep_margin)
263 {
264 if(!m_floppy || m_write_start_time == attotime::never)
265 return;
266
267 // Beware of time travel. Index pulse callback (which flushes)
268 // can be called with a machine().time() inferior to the last
269 // m_write_buffer value if the calling cpu instructions are not
270 // suspendable.
271
272 attotime limit = machine().time();
273 int kept_pos = m_write_position;
274 int kept_count = 0;
275 while(kept_pos > 0 && m_write_buffer[kept_pos-1] >= limit) {
276 kept_pos--;
277 kept_count++;
278 }
279
280 if(keep_margin) {
281 attotime last = kept_pos ? m_write_buffer[kept_pos-1] : m_write_start_time;
282 attotime delta = limit-last;
283 delta = delta / 2;
284 limit = limit - delta;
285 }
286 m_write_position -= kept_count;
287 if(m_write_position && m_write_buffer[0] == m_write_start_time) {
288 if(m_write_position)
289 memmove(m_write_buffer, m_write_buffer+1, sizeof(m_write_buffer[0])*(m_write_position-1));
290 m_write_position--;
291 }
292 m_floppy->write_flux(m_write_start_time, limit, m_write_position, m_write_buffer);
293 m_write_start_time = limit;
294
295 if(kept_count != 0)
296 memmove(m_write_buffer, m_write_buffer+kept_pos, kept_count*sizeof(m_write_buffer[0]));
297 m_write_position = kept_count;
298 }
299