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