1 // license:LGPL-2.1+
2 // copyright-holders:Michael Zapf
3 /****************************************************************************
4 
5     TI-99 Standard Floppy Disk Controller Card
6     Based on WD1771
7     Single Density, Double-sided
8 
9     Michael Zapf
10     September 2010
11     January 2012: rewritten as class (MZ)
12 
13 ****************************************************************************/
14 
15 #include "emu.h"
16 #include "ti_fdc.h"
17 #include "formats/ti99_dsk.h"
18 #include "machine/rescap.h"
19 
20 #define LOG_WARN        (1U<<1)    // Warnings
21 #define LOG_CONFIG      (1U<<2)
22 #define LOG_RW          (1U<<3)
23 #define LOG_PORTS       (1U<<4)    // too noisy in RW
24 #define LOG_CRU         (1U<<5)
25 #define LOG_READY       (1U<<6)
26 #define LOG_SIGNALS     (1U<<7)
27 #define LOG_DRQ         (1U<<8)    // too noisy in SIGNALS
28 #define LOG_DATA        (1U<<9)
29 #define LOG_MOTOR       (1U<<10)
30 #define LOG_ADDRESS     (1U<<11)
31 
32 #define VERBOSE ( LOG_CONFIG | LOG_WARN )
33 #include "logmacro.h"
34 
35 DEFINE_DEVICE_TYPE_NS(TI99_FDC, bus::ti99::peb, ti_fdc_device, "ti99_fdc", "TI-99 Standard DSSD Floppy Controller")
36 
37 namespace bus { namespace ti99 { namespace peb {
38 
39 // ----------------------------------
40 #define FDC_TAG "fd1771"
41 #define MOTOR_TIMER 1
42 
43 #define TI_FDC_TAG "ti_dssd_controller"
44 
ti_fdc_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)45 ti_fdc_device::ti_fdc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
46 	device_t(mconfig, TI99_FDC, tag, owner, clock),
47 	device_ti99_peribox_card_interface(mconfig, *this),
48 	m_address(0),
49 	m_DRQ(0),
50 	m_IRQ(0),
51 	m_HLD(0),
52 	m_DVENA(0),
53 	m_inDsrArea(false),
54 	m_WDsel(false),
55 	m_fd1771(*this, FDC_TAG),
56 	m_crulatch(*this, "crulatch"),
57 	m_motormf(*this, "motormf"),
58 	m_dsrrom(nullptr),
59 	m_sel_floppy(0)
60 {
61 }
62 
63 /*
64     Operate the wait state logic.
65 */
operate_ready_line()66 void ti_fdc_device::operate_ready_line()
67 {
68 	// This is the wait state logic
69 	line_state nready = (m_WDsel &&             // Are we accessing 5ffx (even addr)?
70 			m_crulatch->q2_r() &&               // and the wait state generation is active (SBO 2)
71 			(m_DRQ==CLEAR_LINE) &&              // and we are waiting for a byte
72 			(m_IRQ==CLEAR_LINE) &&              // and there is no interrupt yet
73 			(m_DVENA==ASSERT_LINE)              // and the motor is turning?
74 			)? ASSERT_LINE : CLEAR_LINE;        // In that case, clear READY and thus trigger wait states
75 	LOGMASKED(LOG_READY, "Address=%04x, DRQ=%d, INTRQ=%d, MOTOR=%d -> READY=%d\n", m_address & 0xffff, m_DRQ, m_IRQ, m_DVENA, (nready==CLEAR_LINE)? 1:0);
76 	m_slot->set_ready((nready==CLEAR_LINE)? ASSERT_LINE : CLEAR_LINE);
77 }
78 
79 /*
80  * Callbacks from the FD1771 chip
81  */
WRITE_LINE_MEMBER(ti_fdc_device::fdc_irq_w)82 WRITE_LINE_MEMBER( ti_fdc_device::fdc_irq_w )
83 {
84 	m_IRQ = state? ASSERT_LINE : CLEAR_LINE;
85 	LOGMASKED(LOG_SIGNALS, "INTRQ callback = %d\n", m_IRQ);
86 	operate_ready_line();
87 }
88 
WRITE_LINE_MEMBER(ti_fdc_device::fdc_drq_w)89 WRITE_LINE_MEMBER( ti_fdc_device::fdc_drq_w )
90 {
91 	m_DRQ = state? ASSERT_LINE : CLEAR_LINE;
92 	LOGMASKED(LOG_DRQ, "DRQ callback = %d\n", m_DRQ);
93 	operate_ready_line();
94 }
95 
WRITE_LINE_MEMBER(ti_fdc_device::fdc_hld_w)96 WRITE_LINE_MEMBER( ti_fdc_device::fdc_hld_w )
97 {
98 	m_HLD = state? ASSERT_LINE : CLEAR_LINE;
99 	LOGMASKED(LOG_SIGNALS, "HLD callback = %d\n", m_HLD);
100 }
101 
setaddress_dbin(offs_t offset,int state)102 void ti_fdc_device::setaddress_dbin(offs_t offset, int state)
103 {
104 	// Selection login in the PAL and some circuits on the board
105 
106 	// Is the card being selected?
107 	m_address = offset;
108 	m_inDsrArea = in_dsr_space(offset, true);
109 
110 	if (!m_inDsrArea || !m_selected) return;
111 
112 	LOGMASKED(LOG_ADDRESS, "Set address = %04x\n", offset & 0xffff);
113 
114 	// Is the WD chip on the card being selected?
115 	m_WDsel = m_inDsrArea && ((m_address & 0x1ff1)==0x1ff0);
116 
117 	// Clear or assert the outgoing READY line
118 	operate_ready_line();
119 }
120 
121 /*
122     Access for debugger. This is a stripped-down version of the
123     main methods below. We only allow ROM access.
124 */
debug_read(offs_t offset,uint8_t * value)125 void ti_fdc_device::debug_read(offs_t offset, uint8_t* value)
126 {
127 	if (in_dsr_space(offset, true) && m_selected)
128 	{
129 		if ((offset & 0x1ff1)!=0x1ff0)
130 			*value = m_dsrrom[offset & 0x1fff];
131 	}
132 }
133 
readz(offs_t offset,uint8_t * value)134 void ti_fdc_device::readz(offs_t offset, uint8_t *value)
135 {
136 	if (machine().side_effects_disabled())
137 	{
138 		debug_read(offset, value);
139 		return;
140 	}
141 
142 	if (m_inDsrArea && m_selected)
143 	{
144 		// Read ports of 1771 are mapped to 5FF0,2,4,6: 0101 1111 1111 0xx0
145 		// Note that incoming/outgoing data are inverted for FD1771
146 		uint8_t reply = 0;
147 
148 		if (m_WDsel && ((m_address & 9)==0))
149 		{
150 			if (!machine().side_effects_disabled()) reply = m_fd1771->read((offset >> 1)&0x03);
151 			LOGMASKED(LOG_PORTS, "%04x -> %02x\n", offset & 0xffff, reply);
152 		}
153 		else
154 		{
155 			reply = m_dsrrom[m_address & 0x1fff];
156 			LOGMASKED(LOG_RW, "%04x -> %02x\n", offset & 0xffff, reply);
157 		}
158 		*value = reply;
159 	}
160 }
161 
write(offs_t offset,uint8_t data)162 void ti_fdc_device::write(offs_t offset, uint8_t data)
163 {
164 	// As this is a memory-mapped access we must prevent the debugger
165 	// from messing with the operation
166 	if (machine().side_effects_disabled()) return;
167 
168 	if (m_inDsrArea && m_selected)
169 	{
170 		// Write ports of 1771 are mapped to 5FF8,A,C,E: 0101 1111 1111 1xx0
171 		// This is important for the TI console: The TMS9900 CPU always performs a
172 		// read operation before the write operation, and if we did not use
173 		// different read and write ports, it would attempt to read from the
174 		// controller before passing a command or data
175 		// to it. In the best case, nothing happens; in the worst case, status
176 		// flags may be reset by the read operation.
177 
178 		// Note that incoming/outgoing data are inverted for FD1771
179 		if (m_WDsel)
180 		{
181 			if ((m_address & 9)==8)
182 			{
183 				m_fd1771->write((offset >> 1)&0x03, data);
184 				LOGMASKED(LOG_PORTS, "%04x <- %02x\n", offset & 0xffff, ~data & 0xff);
185 			}
186 			else
187 			{
188 				LOGMASKED(LOG_RW, "%04x <- %02x (ignored)\n", m_address & 0xffff, ~data & 0xff);
189 			}
190 		}
191 	}
192 }
193 
194 /*
195     CRU read access
196 
197        7     6     5     4      3     2     1     0
198     +-----+-----+-----+------+-----+-----+-----+-----+
199     | Side|  1  |  0  |DVENA*| D3C*| D2C*| D1C*| HLD |
200     +-----+-----+-----+------+-----+-----+-----+-----+
201 
202     See schematics for the meaning of the bits.
203 */
crureadz(offs_t offset,uint8_t * value)204 void ti_fdc_device::crureadz(offs_t offset, uint8_t *value)
205 {
206 	if ((offset & 0xff00)==m_cru_base)
207 	{
208 		if ((offset & 0x0070) == 0)
209 		{
210 			switch ((offset >> 1) & 0x07)
211 			{
212 			case 0: *value = (m_HLD==ASSERT_LINE)? 1:0; break;
213 			case 1: *value = (m_crulatch->q4_r()==ASSERT_LINE && m_DVENA==ASSERT_LINE)? 1:0; break;
214 			case 2: *value = (m_crulatch->q5_r()==ASSERT_LINE && m_DVENA==ASSERT_LINE)? 1:0; break;
215 			case 3: *value = (m_crulatch->q6_r()==ASSERT_LINE && m_DVENA==ASSERT_LINE)? 1:0; break;
216 			case 4: *value = (m_DVENA==CLEAR_LINE)? 1:0; break;
217 			case 5: *value = 0; break;
218 			case 6: *value = 1; break;
219 			case 7: *value = (m_crulatch->q7_r()==ASSERT_LINE)? 1:0; break;
220 			}
221 		}
222 		else *value = 0;
223 		LOGMASKED(LOG_CRU, "Read CRU %04x = %02x\n", offset, *value);
224 	}
225 }
226 
cruwrite(offs_t offset,uint8_t data)227 void ti_fdc_device::cruwrite(offs_t offset, uint8_t data)
228 {
229 	if ((offset & 0xff00)==m_cru_base)
230 		m_crulatch->write_bit((offset >> 1) & 0x07, BIT(data, 0));
231 }
232 
WRITE_LINE_MEMBER(ti_fdc_device::dskpgena_w)233 WRITE_LINE_MEMBER(ti_fdc_device::dskpgena_w)
234 {
235 	// (De)select the card. Indicated by a LED on the board.
236 	m_selected = state;
237 	LOGMASKED(LOG_CRU, "Map DSR (bit 0) = %d\n", m_selected);
238 }
239 
240 /*
241     Trigger the motor monoflop.
242 */
WRITE_LINE_MEMBER(ti_fdc_device::kaclk_w)243 WRITE_LINE_MEMBER(ti_fdc_device::kaclk_w)
244 {
245 	m_motormf->b_w(state);
246 }
247 
WRITE_LINE_MEMBER(ti_fdc_device::dvena_w)248 WRITE_LINE_MEMBER(ti_fdc_device::dvena_w)
249 {
250 	m_DVENA = state;
251 	LOGMASKED(LOG_MOTOR, "Motor %s\n", state? "on" : "off");
252 
253 	// The monoflop is connected to the READY line
254 	m_fd1771->set_force_ready(state==ASSERT_LINE);
255 
256 	// Set all motors
257 	for (auto & elem : m_floppy)
258 		if (elem != nullptr) elem->mon_w((state==ASSERT_LINE)? 0 : 1);
259 
260 	// The motor-on line also connects to the wait state logic
261 	operate_ready_line();
262 }
263 
WRITE_LINE_MEMBER(ti_fdc_device::waiten_w)264 WRITE_LINE_MEMBER(ti_fdc_device::waiten_w)
265 {
266 	// Set disk ready/hold (bit 2)
267 	// 0: ignore IRQ and DRQ
268 	// 1: TMS9900 is stopped until IRQ or DRQ are set
269 	// OR the motor stops rotating - rotates for 4.23s after write
270 	// to CRU bit 1
271 	LOGMASKED(LOG_CRU, "Arm wait state logic (bit 2) = %d\n", state);
272 }
273 
WRITE_LINE_MEMBER(ti_fdc_device::hlt_w)274 WRITE_LINE_MEMBER(ti_fdc_device::hlt_w)
275 {
276 	// Load disk heads (HLT pin) (bit 3). Not implemented.
277 	LOGMASKED(LOG_CRU, "Set head load (bit 3) = %d\n", state);
278 }
279 
WRITE_LINE_MEMBER(ti_fdc_device::sidsel_w)280 WRITE_LINE_MEMBER(ti_fdc_device::sidsel_w)
281 {
282 	// Select side of disk (bit 7)
283 	LOGMASKED(LOG_CRU, "Set side (bit 7) = %d\n", state);
284 	if (m_sel_floppy != 0) m_floppy[m_sel_floppy-1]->ss_w(state);
285 }
286 
287 /*
288     Drive selects
289 */
WRITE_LINE_MEMBER(ti_fdc_device::dsel1_w)290 WRITE_LINE_MEMBER(ti_fdc_device::dsel1_w)
291 {
292 	select_drive(1, state);
293 }
294 
WRITE_LINE_MEMBER(ti_fdc_device::dsel2_w)295 WRITE_LINE_MEMBER(ti_fdc_device::dsel2_w)
296 {
297 	select_drive(2, state);
298 }
299 
WRITE_LINE_MEMBER(ti_fdc_device::dsel3_w)300 WRITE_LINE_MEMBER(ti_fdc_device::dsel3_w)
301 {
302 	select_drive(3, state);
303 }
304 
select_drive(int n,int state)305 void ti_fdc_device::select_drive(int n, int state)
306 {
307 	if (state == CLEAR_LINE)
308 	{
309 		LOGMASKED(LOG_CRU, "Unselect drive DSK%d\n", n);
310 
311 		// Only when no bit is set, unselect all drives.
312 		if ((m_crulatch->q4_r() == 0) && (m_crulatch->q5_r() == 0)
313 			&& (m_crulatch->q6_r() == 0))
314 		{
315 			m_fd1771->set_floppy(nullptr);
316 			m_sel_floppy = 0;
317 		}
318 	}
319 	else
320 	{
321 		LOGMASKED(LOG_CRU, "Select drive DSK%d\n", n);
322 		if (m_sel_floppy != 0 && m_sel_floppy != n)
323 		{
324 			LOGMASKED(LOG_WARN, "Warning: DSK%d selected while DSK%d not yet unselected\n", n, m_sel_floppy);
325 		}
326 
327 		if (m_floppy[n-1] != nullptr)
328 		{
329 			m_sel_floppy = n;
330 			m_fd1771->set_floppy(m_floppy[n-1]);
331 			m_floppy[n-1]->ss_w(m_crulatch->q7_r());
332 		}
333 	}
334 }
335 
device_start()336 void ti_fdc_device::device_start()
337 {
338 	m_dsrrom = memregion(TI99_DSRROM)->base();
339 	m_cru_base = 0x1100;
340 
341 	save_item(NAME(m_address));
342 	save_item(NAME(m_DRQ));
343 	save_item(NAME(m_IRQ));
344 	save_item(NAME(m_DVENA));
345 	save_item(NAME(m_inDsrArea));
346 	save_item(NAME(m_WDsel));
347 	save_item(NAME(m_sel_floppy));
348 }
349 
device_reset()350 void ti_fdc_device::device_reset()
351 {
352 	m_DRQ = CLEAR_LINE;
353 	m_IRQ = CLEAR_LINE;
354 	m_DVENA = CLEAR_LINE;
355 	m_fd1771->set_force_ready(false);
356 
357 	m_selected = false;
358 	m_inDsrArea = false;
359 	m_WDsel = false;
360 
361 	for (int i=0; i < 3; i++)
362 	{
363 		if (m_floppy[i] != nullptr)
364 			LOGMASKED(LOG_CONFIG, "Connector %d with %s\n", i, m_floppy[i]->name());
365 		else
366 			LOGMASKED(LOG_CONFIG, "No floppy attached to connector %d\n", i);
367 	}
368 
369 	m_sel_floppy = 0;
370 }
371 
device_config_complete()372 void ti_fdc_device::device_config_complete()
373 {
374 	// Seems to be null when doing a "-listslots"
375 	for (auto &elem : m_floppy)
376 		elem = nullptr;
377 	if (subdevice("0")!=nullptr) m_floppy[0] = static_cast<floppy_image_device*>(subdevice("0")->subdevices().first());
378 	if (subdevice("1")!=nullptr) m_floppy[1] = static_cast<floppy_image_device*>(subdevice("1")->subdevices().first());
379 	if (subdevice("2")!=nullptr) m_floppy[2] = static_cast<floppy_image_device*>(subdevice("2")->subdevices().first());
380 }
381 
FLOPPY_FORMATS_MEMBER(ti_fdc_device::floppy_formats)382 FLOPPY_FORMATS_MEMBER(ti_fdc_device::floppy_formats)
383 	FLOPPY_TI99_SDF_FORMAT,
384 	FLOPPY_TI99_TDF_FORMAT
385 FLOPPY_FORMATS_END
386 
387 static void tifdc_floppies(device_slot_interface &device)
388 {
389 	device.option_add("525dd", FLOPPY_525_DD);
390 }
391 
392 ROM_START( ti_fdc )
393 	ROM_REGION(0x2000, TI99_DSRROM, 0)
394 	ROM_LOAD("fdc_dsr.u26", 0x0000, 0x1000, CRC(693c6b6e) SHA1(0c24fb4944843ad3f08b0b139244a6bb05e1c6c2)) /* TI disk DSR ROM first 4K */
395 	ROM_LOAD("fdc_dsr.u27", 0x1000, 0x1000, CRC(2c921087) SHA1(3646c3bcd2dce16b918ee01ea65312f36ae811d2)) /* TI disk DSR ROM second 4K */
396 ROM_END
397 
device_add_mconfig(machine_config & config)398 void ti_fdc_device::device_add_mconfig(machine_config& config)
399 {
400 	FD1771(config, m_fd1771, 2_MHz_XTAL / 2);
401 	m_fd1771->intrq_wr_callback().set(FUNC(ti_fdc_device::fdc_irq_w));
402 	m_fd1771->drq_wr_callback().set(FUNC(ti_fdc_device::fdc_drq_w));
403 	m_fd1771->hld_wr_callback().set(FUNC(ti_fdc_device::fdc_hld_w));
404 
405 	FLOPPY_CONNECTOR(config, "0", tifdc_floppies, "525dd", ti_fdc_device::floppy_formats).enable_sound(true);
406 	FLOPPY_CONNECTOR(config, "1", tifdc_floppies, "525dd", ti_fdc_device::floppy_formats).enable_sound(true);
407 	FLOPPY_CONNECTOR(config, "2", tifdc_floppies, nullptr, ti_fdc_device::floppy_formats).enable_sound(true);
408 
409 	LS259(config, m_crulatch); // U23
410 	m_crulatch->q_out_cb<0>().set(FUNC(ti_fdc_device::dskpgena_w));
411 	m_crulatch->q_out_cb<1>().set(FUNC(ti_fdc_device::kaclk_w));
412 	m_crulatch->q_out_cb<2>().set(FUNC(ti_fdc_device::waiten_w));
413 	m_crulatch->q_out_cb<3>().set(FUNC(ti_fdc_device::hlt_w));
414 	m_crulatch->q_out_cb<4>().set(FUNC(ti_fdc_device::dsel1_w));
415 	m_crulatch->q_out_cb<5>().set(FUNC(ti_fdc_device::dsel2_w));
416 	m_crulatch->q_out_cb<6>().set(FUNC(ti_fdc_device::dsel3_w));
417 	m_crulatch->q_out_cb<7>().set(FUNC(ti_fdc_device::sidsel_w));
418 
419 	TTL74123(config, m_motormf, 0);
420 	m_motormf->out_cb().set(FUNC(ti_fdc_device::dvena_w));
421 	m_motormf->set_connection_type(TTL74123_GROUNDED);
422 	m_motormf->set_resistor_value(RES_K(200));
423 	m_motormf->set_capacitor_value(CAP_U(47));
424 	m_motormf->set_clear_pin_value(1);
425 }
426 
device_rom_region() const427 const tiny_rom_entry *ti_fdc_device::device_rom_region() const
428 {
429 	return ROM_NAME( ti_fdc );
430 }
431 
432 } } } // end namespace bus::ti99::peb
433