1 // license:BSD-3-Clause
2 // copyright-holders:Dirk Best
3 /***************************************************************************
4
5 Western Digital WD1010-05 Winchester Disk Controller
6
7 ***************************************************************************/
8
9 #include "emu.h"
10 #include "wd1010.h"
11
12 //#define LOG_GENERAL (1U << 0)
13 #define LOG_CMD (1U << 1)
14 #define LOG_INT (1U << 2)
15 #define LOG_SEEK (1U << 3)
16 #define LOG_REGS (1U << 4)
17 #define LOG_DATA (1U << 5)
18
19 #define VERBOSE (LOG_CMD | LOG_INT | LOG_SEEK | LOG_REGS | LOG_DATA)
20 //#define LOG_OUTPUT_STREAM std::cout
21
22 #include "logmacro.h"
23
24 #define LOGCMD(...) LOGMASKED(LOG_CMD, __VA_ARGS__)
25 #define LOGINT(...) LOGMASKED(LOG_INT, __VA_ARGS__)
26 #define LOGSEEK(...) LOGMASKED(LOG_SEEK, __VA_ARGS__)
27 #define LOGREGS(...) LOGMASKED(LOG_REGS, __VA_ARGS__)
28 #define LOGDATA(...) LOGMASKED(LOG_DATA, __VA_ARGS__)
29
30
31 //**************************************************************************
32 // DEVICE DEFINITIONS
33 //**************************************************************************
34
35 DEFINE_DEVICE_TYPE(WD1010, wd1010_device, "wd1010", "Western Digital WD1010-05")
36
37
38 //**************************************************************************
39 // LIVE DEVICE
40 //**************************************************************************
41
42 //-------------------------------------------------
43 // wd1010_device - constructor
44 //-------------------------------------------------
45
wd1010_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)46 wd1010_device::wd1010_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
47 device_t(mconfig, WD1010, tag, owner, clock),
48 m_out_intrq_cb(*this),
49 m_out_bdrq_cb(*this),
50 m_out_bcs_cb(*this),
51 m_out_bcr_cb(*this),
52 m_out_dirin_cb(*this),
53 m_out_wg_cb(*this),
54 m_in_data_cb(*this),
55 m_out_data_cb(*this),
56 m_intrq(0),
57 m_brdy(0),
58 m_stepping_rate(0x00),
59 m_command(0x00),
60 m_error(0x00),
61 m_precomp(0x00),
62 m_sector_count(0x00),
63 m_sector_number(0x00),
64 m_cylinder(0x0000),
65 m_sdh(0x00),
66 m_status(0x00)
67 {
68 }
69
70 //-------------------------------------------------
71 // device_start - device-specific startup
72 //-------------------------------------------------
73
device_start()74 void wd1010_device::device_start()
75 {
76 // get connected drives
77 for (int i = 0; i < 4; i++)
78 {
79 char name[2];
80 name[0] = '0' + i;
81 name[1] = 0;
82 m_drives[i].drive = subdevice<harddisk_image_device>(name);
83 m_drives[i].head = 0;
84 m_drives[i].cylinder = 0;
85 m_drives[i].sector = 0;
86 }
87
88 // resolve callbacks
89 m_out_intrq_cb.resolve_safe();
90 m_out_bdrq_cb.resolve_safe();
91 m_out_bcs_cb.resolve_safe();
92 m_out_bcr_cb.resolve_safe();
93 m_out_dirin_cb.resolve_safe();
94 m_out_wg_cb.resolve_safe();
95 m_in_data_cb.resolve_safe(0);
96 m_out_data_cb.resolve_safe();
97
98 // allocate timer
99 m_seek_timer = timer_alloc(TIMER_SEEK);
100 m_read_timer = timer_alloc(TIMER_READ);
101 m_write_timer = timer_alloc(TIMER_WRITE);
102
103 // register for save states
104 save_item(NAME(m_intrq));
105 save_item(NAME(m_brdy));
106 save_item(NAME(m_stepping_rate));
107 save_item(NAME(m_command));
108 save_item(NAME(m_error));
109 save_item(NAME(m_precomp));
110 save_item(NAME(m_sector_count));
111 save_item(NAME(m_sector_number));
112 save_item(NAME(m_cylinder));
113 save_item(NAME(m_sdh));
114 save_item(NAME(m_status));
115 }
116
117 //-------------------------------------------------
118 // device_reset - device-specific reset
119 //-------------------------------------------------
120
device_reset()121 void wd1010_device::device_reset()
122 {
123 }
124
125 //-------------------------------------------------
126 // device_timer - device-specific timer
127 //-------------------------------------------------
128
device_timer(emu_timer & timer,device_timer_id tid,int param,void * ptr)129 void wd1010_device::device_timer(emu_timer &timer, device_timer_id tid, int param, void *ptr)
130 {
131 switch (tid)
132 {
133 case TIMER_SEEK:
134
135 if ((m_command >> 4) != CMD_SCAN_ID)
136 {
137 LOGSEEK("Seek complete\n");
138 m_drives[drive()].cylinder = param;
139 m_status |= STATUS_SC;
140 }
141
142 switch (m_command >> 4)
143 {
144 case CMD_RESTORE:
145 cmd_restore();
146 break;
147
148 case CMD_SEEK:
149 cmd_seek();
150 break;
151
152 case CMD_READ_SECTOR:
153 cmd_read_sector();
154 break;
155
156 case CMD_WRITE_SECTOR:
157 case CMD_WRITE_FORMAT:
158 cmd_write_sector();
159 break;
160
161 case CMD_SCAN_ID:
162 cmd_scan_id();
163 break;
164 }
165
166 break;
167
168 case TIMER_READ:
169 cmd_read_sector();
170 break;
171
172 case TIMER_WRITE:
173 cmd_write_sector();
174 break;
175 }
176 }
177
178 //-------------------------------------------------
179 // set_error - set error and adjust status
180 //-------------------------------------------------
181
set_error(int error)182 void wd1010_device::set_error(int error)
183 {
184 if (error)
185 {
186 m_error |= error;
187 m_status |= STATUS_ERR;
188 }
189 else
190 {
191 m_error = 0;
192 m_status &= ~STATUS_ERR;
193 }
194 }
195
196 //-------------------------------------------------
197 // set_intrq - set interrupt status
198 //-------------------------------------------------
199
set_intrq(int state)200 void wd1010_device::set_intrq(int state)
201 {
202 if (m_intrq == 0 && state == 1)
203 {
204 LOGINT("INT 1\n");
205 m_intrq = 1;
206 m_out_intrq_cb(1);
207 }
208 else if (m_intrq == 1 && state == 0)
209 {
210 LOGINT("INT 0\n");
211 m_intrq = 0;
212 m_out_intrq_cb(0);
213 }
214 }
215
216 //-------------------------------------------------
217 // set_bdrq - set drq status
218 //-------------------------------------------------
219
set_bdrq(int state)220 void wd1010_device::set_bdrq(int state)
221 {
222 if ((!(m_status & STATUS_DRQ)) && state == 1)
223 {
224 LOGINT("DRQ 1\n");
225 m_status |= STATUS_DRQ;
226 m_out_bdrq_cb(1);
227 }
228 else if ((m_status & STATUS_DRQ) && state == 0)
229 {
230 LOGINT("DRQ 0\n");
231 m_status &= ~STATUS_DRQ;
232 m_out_bdrq_cb(0);
233 }
234 }
235
236 //-------------------------------------------------
237 // get_stepping_rate - calculate stepping rate
238 //-------------------------------------------------
239
get_stepping_rate()240 attotime wd1010_device::get_stepping_rate()
241 {
242 if (m_stepping_rate)
243 return attotime::from_usec(500 * m_stepping_rate);
244 else
245 return attotime::from_usec(35);
246 }
247
248 //-------------------------------------------------
249 // start_command - executed at the start of every command
250 //-------------------------------------------------
251
start_command()252 void wd1010_device::start_command()
253 {
254 // now busy and command in progress, clear error
255 m_status |= STATUS_BSY;
256 m_status |= STATUS_CIP;
257 set_error(0);
258
259 switch (m_command >> 4)
260 {
261 case CMD_RESTORE:
262 m_stepping_rate = m_command & 0x0f;
263 LOGCMD("RESTORE\n");
264 break;
265 case CMD_READ_SECTOR:
266 LOGCMD("READ SECTOR\n");
267 break;
268 case CMD_WRITE_SECTOR:
269 LOGCMD("WRITE SECTOR\n");
270 break;
271 case CMD_SCAN_ID:
272 LOGCMD("SCAN ID\n");
273 break;
274 case CMD_WRITE_FORMAT:
275 LOGCMD("WRITE FORMAT ID\n");
276 break;
277 case CMD_SEEK:
278 m_stepping_rate = m_command & 0x0f;
279 LOGCMD("SEEK\n");
280 break;
281 }
282 }
283
284 //-------------------------------------------------
285 // end_command - executed at end of every command
286 //-------------------------------------------------
287
end_command()288 void wd1010_device::end_command()
289 {
290 m_out_bcr_cb(1);
291 m_out_bcr_cb(0);
292
293 m_status &= ~(STATUS_BSY | STATUS_CIP);
294
295 set_intrq(1);
296 }
297
298 //-------------------------------------------------
299 // get_lbasector - translate to lba
300 //-------------------------------------------------
301
get_lbasector()302 int wd1010_device::get_lbasector()
303 {
304 hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
305 hard_disk_info *info = hard_disk_get_info(file);
306 int lbasector;
307
308 lbasector = m_cylinder;
309 lbasector *= info->heads;
310 lbasector += head();
311 lbasector *= info->sectors;
312 lbasector += m_sector_number;
313
314 return lbasector;
315 }
316
317
318 //**************************************************************************
319 // INTERFACE
320 //**************************************************************************
321
drdy_w(int state)322 void wd1010_device::drdy_w(int state)
323 {
324 if (state)
325 m_status |= STATUS_RDY;
326 else
327 m_status &= ~STATUS_RDY;
328 }
329
brdy_w(int state)330 void wd1010_device::brdy_w(int state)
331 {
332 m_brdy = state;
333 }
334
sc_r()335 int wd1010_device::sc_r()
336 {
337 return m_status & STATUS_SC ? 1 : 0;
338 }
339
tk000_r()340 int wd1010_device::tk000_r()
341 {
342 return m_drives[drive()].cylinder == 0 ? 1 : 0;
343 }
344
read(offs_t offset)345 uint8_t wd1010_device::read(offs_t offset)
346 {
347 // if the controller is busy all reads return the status register
348 if (m_status & STATUS_BSY)
349 {
350 LOG("Read while busy, return STATUS: %02x\n", m_status);
351 return m_status;
352 }
353
354 switch (offset & 0x07)
355 {
356 case 0:
357 LOGREGS("RD INVALID\n");
358 return 0;
359
360 case 1:
361 LOGREGS("RD ERROR: %02x\n", m_error);
362 return m_error;
363
364 case 2:
365 LOGREGS("RD SECTOR COUNT: %02x\n", m_sector_count);
366 return m_sector_count;
367
368 case 3:
369 LOGREGS("RD SECTOR NUMBER: %02x\n", m_sector_number);
370 return m_sector_number;
371
372 case 4:
373 LOGREGS("RD CYLINDER L: %02x\n", m_cylinder & 0xff);
374 return m_cylinder & 0xff;
375
376 case 5:
377 LOGREGS("RD CYLINDER H: %02x\n", m_cylinder >> 8);
378 return m_cylinder >> 8;
379
380 case 6:
381 LOGREGS("RD SDH: %02x\n", m_sdh);
382 return m_sdh;
383
384 case 7:
385 LOGREGS("RD STATUS: %02x\n", m_status);
386
387 // reading the status register clears the interrupt
388 if (!machine().side_effects_disabled())
389 set_intrq(0);
390
391 return m_status;
392 }
393
394 // should never get here
395 return 0xff;
396 }
397
write(offs_t offset,uint8_t data)398 void wd1010_device::write(offs_t offset, uint8_t data)
399 {
400 switch (offset & 0x07)
401 {
402 case 0:
403 LOGREGS("WR INVALID: %02x\n", data);
404 break;
405
406 case 1:
407 LOGREGS("WR PRECOMP: %02x\n", data);
408 m_precomp = data;
409 break;
410
411 case 2:
412 LOGREGS("WR SECTOR COUNT: %02x\n", data);
413 m_sector_count = data;
414 break;
415
416 case 3:
417 LOGREGS("WR SECTOR NUMBER: %02x\n", data);
418 m_sector_number = data;
419 break;
420
421 case 4:
422 LOGREGS("WR CYLINDER L: %02x\n", data);
423 m_cylinder = (m_cylinder & 0xff00) | (data << 0);
424 break;
425
426 case 5:
427 LOGREGS("WR CYLINDER H: %02x\n", data);
428 m_cylinder = (m_cylinder & 0x00ff) | (data << 8);
429 break;
430
431 case 6:
432 LOGREGS("WR SDH: %02x\n", data);
433 m_sdh = data;
434 break;
435
436 case 7:
437 // writes to the command register are ignored when a command is in progress
438 if (m_status & STATUS_CIP)
439 return;
440
441 // writing the command register clears the interrupt
442 set_intrq(0);
443
444 // check drive status
445 if (!(m_status & STATUS_RDY))
446 {
447 // selected drive not ready
448 LOG("--> Drive not ready, aborting\n");
449
450 set_error(ERR_AC);
451 end_command();
452 }
453 else
454 {
455 m_command = data;
456
457 start_command();
458
459 // all other command imply a seek
460 if ((m_command >> 4) != CMD_SCAN_ID)
461 m_status &= ~STATUS_SC;
462
463 int seek = 0;
464 int target = 0;
465
466 switch (m_command >> 4)
467 {
468 case CMD_RESTORE:
469 seek = m_drives[drive()].cylinder;
470 target = 0;
471 break;
472
473 case CMD_SEEK:
474 case CMD_READ_SECTOR:
475 case CMD_WRITE_SECTOR:
476 case CMD_WRITE_FORMAT:
477 seek = m_drives[drive()].cylinder - m_cylinder;
478 target = m_cylinder;
479 break;
480 }
481
482 m_out_dirin_cb(seek > 0 ? 1 : 0);
483
484 if ((m_command >> 4) != CMD_SCAN_ID)
485 LOGSEEK("Seeking %d cylinders to %d\n", seek, target);
486
487 m_seek_timer->adjust(get_stepping_rate() * abs(seek), target);
488 }
489
490 break;
491 }
492 }
493
494
495 //**************************************************************************
496 // IMPLEMENTATION
497 //**************************************************************************
498
cmd_restore()499 void wd1010_device::cmd_restore()
500 {
501 LOGCMD("--> RESTORE done\n");
502 end_command();
503 }
504
cmd_read_sector()505 void wd1010_device::cmd_read_sector()
506 {
507 if (m_status & STATUS_DRQ)
508 {
509 if (m_brdy)
510 {
511 set_bdrq(0);
512
513 // if there's nothing left we're done
514 if (m_sector_count == 0)
515 {
516 end_command();
517 return;
518 }
519 }
520 else
521 {
522 // continue waiting for the buffer to be ready
523 m_read_timer->adjust(attotime::from_usec(35));
524 return;
525 }
526 }
527
528 hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
529 hard_disk_info *info = hard_disk_get_info(file);
530
531 // verify that we can read
532 if (head() > info->heads)
533 {
534 // out of range
535 LOG("--> Head out of range, aborting\n");
536
537 set_error(ERR_AC);
538 end_command();
539 return;
540 }
541
542 uint8_t buffer[512];
543
544 m_out_bcr_cb(1);
545 m_out_bcr_cb(0);
546
547 m_out_bcs_cb(1);
548
549 LOGDATA("--> Transferring sector to buffer (lba = %08x)\n", get_lbasector());
550
551 hard_disk_read(file, get_lbasector(), buffer);
552
553 for (int i = 0; i < 512; i++)
554 m_out_data_cb(buffer[i]);
555
556 // multi-sector read
557 if (BIT(m_command, 2))
558 {
559 m_sector_number++;
560 m_sector_count--;
561 }
562 else
563 {
564 m_sector_count = 0;
565 }
566
567 if (m_sector_count == 0)
568 {
569 m_out_bcr_cb(1);
570 m_out_bcr_cb(0);
571 }
572
573 m_out_bcs_cb(0);
574
575 set_bdrq(1);
576
577 // interrupt at bdrq time?
578 if (BIT(m_command, 3) == 0)
579 set_intrq(1);
580
581 // now wait for brdy
582 m_read_timer->adjust(attotime::from_usec(35));
583 }
584
cmd_write_sector()585 void wd1010_device::cmd_write_sector()
586 {
587 set_bdrq(1);
588
589 // wait if the buffer isn't ready
590 if (m_brdy == 0)
591 {
592 m_write_timer->adjust(attotime::from_usec(35));
593 return;
594 }
595
596 hard_disk_file *file = m_drives[drive()].drive->get_hard_disk_file();
597 uint8_t buffer[512];
598
599 set_bdrq(0);
600
601 m_out_bcr_cb(1);
602 m_out_bcr_cb(0);
603
604 m_out_bcs_cb(1);
605
606 m_out_wg_cb(1);
607
608 if ((m_command >> 4) == CMD_WRITE_FORMAT)
609 {
610 // we ignore the format specification and fill everything with 0xe5
611 std::fill(std::begin(buffer), std::end(buffer), 0xe5);
612 }
613 else
614 {
615 // get data for sector from buffer chip
616 for (int i = 0; i < 512; i++)
617 buffer[i] = m_in_data_cb();
618 }
619
620 hard_disk_write(file, get_lbasector(), buffer);
621
622 // save last read head and sector number
623 m_drives[drive()].head = head();
624 m_drives[drive()].sector = m_sector_number;
625
626 // multi-sector write
627 if (BIT(m_command, 2) && m_sector_count > 0)
628 {
629 m_sector_number++;
630 m_sector_count--;
631
632 // schedule another run if we aren't finished yet
633 if (m_sector_count > 0)
634 {
635 m_out_bcs_cb(0);
636 m_out_wg_cb(0);
637 m_write_timer->adjust(attotime::from_usec(35));
638 return;
639 }
640 }
641
642 m_out_bcs_cb(0);
643 m_out_wg_cb(0);
644
645 end_command();
646 }
647
cmd_scan_id()648 void wd1010_device::cmd_scan_id()
649 {
650 // update head, sector size, sector number and cylinder
651 m_sdh = (m_sdh & 0xf8) | (m_drives[drive()].head & 0x07);
652 m_sector_number = m_drives[drive()].sector;
653 m_cylinder = m_drives[drive()].cylinder;
654
655 end_command();
656 }
657
cmd_seek()658 void wd1010_device::cmd_seek()
659 {
660 LOGCMD("--> SEEK done\n");
661 end_command();
662 }
663