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