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