src/wd279x.c

changeset 48
d52688dad7fd
child 49
545798274dad
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/wd279x.c	Sun Dec 05 03:55:46 2010 +0000
     1.3 @@ -0,0 +1,375 @@
     1.4 +#include <stdint.h>
     1.5 +#include <stdbool.h>
     1.6 +#include "wd279x.h"
     1.7 +
     1.8 +
     1.9 +/// WD2797 command constants
    1.10 +enum {
    1.11 +	CMD_MASK				= 0xF0,		///< Bit mask to detect command bits
    1.12 +	CMD_RESTORE				= 0x00,		///< Restore (recalibrate, seek to track 0)
    1.13 +	CMD_SEEK				= 0x10,		///< Seek to given track
    1.14 +	CMD_STEP				= 0x20,		///< Step
    1.15 +	CMD_STEP_TU				= 0x30,		///< Step and update track register
    1.16 +	CMD_STEPIN				= 0x40,		///< Step In
    1.17 +	CMD_STEPIN_TU			= 0x50,		///< Step In and update track register
    1.18 +	CMD_STEPOUT				= 0x60,		///< Step Out
    1.19 +	CMD_STEPOUT_TU			= 0x70,		///< Step Out and update track register
    1.20 +	CMD_READ_SECTOR			= 0x80,		///< Read Sector
    1.21 +	CMD_READ_SECTOR_MULTI	= 0x90,		///< Read Multiple Sectors
    1.22 +	CMD_WRITE_SECTOR		= 0xA0,		///< Write Sector
    1.23 +	CMD_WRITE_SECTOR_MULTI	= 0xB0,		///< Write Multiple Sectors
    1.24 +	CMD_READ_ADDRESS		= 0xC0,		///< Read Address (IDAM contents)
    1.25 +	CMD_FORCE_INTERRUPT		= 0xD0,		///< Force Interrupt
    1.26 +	CMD_READ_TRACK			= 0xE0,		///< Read Track
    1.27 +	CMD_FORMAT_TRACK		= 0xF0		///< Format Track
    1.28 +};
    1.29 +
    1.30 +
    1.31 +/**
    1.32 + * @brief	Initialise a WD2797 context.
    1.33 + */
    1.34 +void wd2797_init(WD279X_CTX *ctx)
    1.35 +{
    1.36 +	// track, head and sector unknown
    1.37 +	ctx->track = 0;
    1.38 +	ctx->head = 0;
    1.39 +	ctx->sector = 0;
    1.40 +
    1.41 +	// no IRQ or DRQ, no IRQ callback
    1.42 +	ctx->irql = false;
    1.43 +	ctx->irqe = false;
    1.44 +
    1.45 +	// no data available
    1.46 +	ctx->data_pos = 0;
    1.47 +	ctx->data_len = 0;
    1.48 +
    1.49 +	// No disc image loaded
    1.50 +	ctx->disc_image = NULL;
    1.51 +}
    1.52 +
    1.53 +
    1.54 +/**
    1.55 + * @brief	Read IRQ Rising Edge status. Clears Rising Edge status if it is set.
    1.56 + * @note	No more IRQs will be sent until the Status Register is read, or a new command is written to the CR.
    1.57 + */
    1.58 +bool wd279x_get_irq(WD279X_CTX *ctx)
    1.59 +{
    1.60 +	// If an IRQ is pending, clear it and return true, otherwise return false
    1.61 +	if (ctx->irqe) {
    1.62 +		ctx->irqe = false;
    1.63 +		return true;
    1.64 +	} else {
    1.65 +		return false;
    1.66 +	}
    1.67 +}
    1.68 +
    1.69 +
    1.70 +/**
    1.71 + * @brief	Read DRQ status.
    1.72 + */
    1.73 +bool wd279x_get_drq(WD279X_CTX *ctx)
    1.74 +{
    1.75 +	return (ctx->data_pos < ctx->data_len);
    1.76 +}
    1.77 +
    1.78 +
    1.79 +/**
    1.80 + * @brief	Read WD279x register.
    1.81 + */
    1.82 +uint8_t wd279x_read_reg(WD279X_CTX *ctx, uint8_t addr)
    1.83 +{
    1.84 +	uint8_t temp = 0;
    1.85 +
    1.86 +	switch (addr & 0x03) {
    1.87 +		case WD279X_REG_STATUS:		// Status register
    1.88 +			// Read from status register clears IRQ
    1.89 +			ctx->irql = false;
    1.90 +
    1.91 +			// Get current status flags (set by last command)
    1.92 +			temp = ctx->status;
    1.93 +			// DRQ bit
    1.94 +			if (ctx->cmd_has_drq)
    1.95 +				temp |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
    1.96 +			// FDC is busy if there is still data in the buffer
    1.97 +			temp |= (ctx->data_pos < ctx->data_len) ? 0x01 : 0x00;	// if data in buffer, then DMA hasn't copied it yet, and we're still busy!
    1.98 +																		// TODO: also if seek delay / read delay hasn't passed (but that's for later)
    1.99 +			return temp;
   1.100 +
   1.101 +		case WD279X_REG_TRACK:		// Track register
   1.102 +			return ctx->track;
   1.103 +
   1.104 +		case WD279X_REG_SECTOR:		// Sector register
   1.105 +			return ctx->sector;
   1.106 +
   1.107 +		case WD279X_REG_DATA:		// Data register
   1.108 +			// If there's data in the buffer, return it. Otherwise return 0xFF.
   1.109 +			if (ctx->data_pos < ctx->data_len) {
   1.110 +				// return data byte and increment pointer
   1.111 +				return ctx->data[ctx->data_pos++];
   1.112 +			} else {
   1.113 +				return 0xff;
   1.114 +			}
   1.115 +
   1.116 +		default:
   1.117 +			// shut up annoying compilers which don't recognise unreachable code when they see it
   1.118 +			// (here's looking at you, gcc!)
   1.119 +			return 0xff;
   1.120 +	}
   1.121 +}
   1.122 +
   1.123 +
   1.124 +/**
   1.125 + * Write WD279X register
   1.126 + */
   1.127 +void wd279x_write_reg(WD279X_CTX *ctx, uint8_t addr, uint8_t val)
   1.128 +{
   1.129 +	uint8_t cmd = val & CMD_MASK;
   1.130 +	size_t lba;
   1.131 +	bool is_type1 = false;
   1.132 +
   1.133 +	switch (addr) {
   1.134 +		case WD279X_REG_COMMAND:	// Command register
   1.135 +			// write to command register clears interrupt request
   1.136 +			ctx->irql = false;
   1.137 +
   1.138 +			// Is the drive ready?
   1.139 +			if (ctx->disc_image == NULL) {
   1.140 +				// No disc image, thus the drive is busy.
   1.141 +				ctx->status = 0x80;
   1.142 +				return;
   1.143 +			}
   1.144 +
   1.145 +			// Handle Type 1 commands
   1.146 +			switch (cmd) {
   1.147 +				case CMD_RESTORE:
   1.148 +					// Restore. Set track to 0 and throw an IRQ.
   1.149 +					is_type1 = true;
   1.150 +					ctx->track = 0;
   1.151 +					break;
   1.152 +
   1.153 +				case CMD_SEEK:
   1.154 +					// Seek. Seek to the track specced in the Data Register.
   1.155 +					is_type1 = true;
   1.156 +					if (ctx->data_reg < ctx->geom_tracks) {
   1.157 +						ctx->track = ctx->data_reg;
   1.158 +					} else {
   1.159 +						// Seek error. :(
   1.160 +						ctx->status = 0x10;
   1.161 +					}
   1.162 +
   1.163 +				case CMD_STEP:
   1.164 +					// TODO! deal with trk0!
   1.165 +					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   1.166 +					is_type1 = true;
   1.167 +					break;
   1.168 +
   1.169 +				case CMD_STEPIN:
   1.170 +				case CMD_STEPOUT:
   1.171 +					// TODO! deal with trk0!
   1.172 +					// Need to keep a copy of the track register; when it hits 0, set the TRK0 flag.
   1.173 +					if (cmd == CMD_STEPIN) {
   1.174 +						ctx->last_step_dir = 1;
   1.175 +					} else {
   1.176 +						ctx->last_step_dir = -1;
   1.177 +					}
   1.178 +					is_type1 = true;
   1.179 +					break;
   1.180 +
   1.181 +				case CMD_STEP_TU:
   1.182 +				case CMD_STEPIN_TU:
   1.183 +				case CMD_STEPOUT_TU:
   1.184 +					// if this is a Step In or Step Out cmd, set the step-direction
   1.185 +					if (cmd == CMD_STEPIN_TU) {
   1.186 +						ctx->last_step_dir = 1;
   1.187 +					} else if (cmd == CMD_STEPOUT_TU) {
   1.188 +						ctx->last_step_dir = -1;
   1.189 +					}
   1.190 +
   1.191 +					// Seek one step in the last direction used.
   1.192 +					ctx->track += ctx->last_step_dir;
   1.193 +					if (ctx->track < 0) ctx->track = 0;
   1.194 +					if (ctx->track >= ctx->geom_tracks) {
   1.195 +						// Seek past end of disc... that'll be a Seek Error then.
   1.196 +						ctx->status = 0x10;
   1.197 +						ctx->track = ctx->geom_tracks - 1;
   1.198 +					}
   1.199 +					is_type1 = true;
   1.200 +					break;
   1.201 +
   1.202 +				default:
   1.203 +					break;
   1.204 +			}
   1.205 +
   1.206 +			if (is_type1) {
   1.207 +				// Terminate any sector reads or writes
   1.208 +				ctx->data_len = ctx->data_pos = 0;
   1.209 +
   1.210 +				// No DRQ bit for these commands.
   1.211 +				ctx->cmd_has_drq = false;
   1.212 +
   1.213 +				// Type1 status byte...
   1.214 +				ctx->status = 0;
   1.215 +				// S7 = Not Ready. Command executed, therefore the drive was ready... :)
   1.216 +				// S6 = Write Protect. TODO: add this
   1.217 +				// S5 = Head Loaded. For certain emulation-related reasons, the heads are always loaded...
   1.218 +				ctx->status |= 0x20;
   1.219 +				// S4 = Seek Error. Not bloody likely if we got down here...!
   1.220 +				// S3 = CRC Error. Not gonna happen on a disc image!
   1.221 +				// S2 = Track 0
   1.222 +				ctx->status |= (ctx->track == 0) ? 0x04 : 0x00;
   1.223 +				// S1 = Index Pulse. TODO -- need periodics to emulate this
   1.224 +				// S0 = Busy. We just exec'd the command, thus we're not busy.
   1.225 +				// 		TODO: Set a timer for seeks, and ONLY clear BUSY when that timer expires. Need periodics for that.
   1.226 +				
   1.227 +				// Set IRQ only if IRQL has been cleared (no pending IRQs)
   1.228 +				ctx->irqe = !ctx->irql;
   1.229 +				ctx->irql = true;
   1.230 +				return;
   1.231 +			}
   1.232 +
   1.233 +			// That's the Type 1 (seek) commands sorted. Now for the others.
   1.234 +
   1.235 +			// If drive isn't ready, then set status B7 and exit
   1.236 +			if (ctx->disc_image == NULL) {
   1.237 +				ctx->status = 0x80;
   1.238 +				return;
   1.239 +			}
   1.240 +
   1.241 +			// If this is a Write command, check write protect status too
   1.242 +			// TODO!
   1.243 +			if (false) {
   1.244 +				// Write protected disc...
   1.245 +				if ((cmd == CMD_WRITE_SECTOR) || (cmd == CMD_WRITE_SECTOR_MULTI) || (cmd == CMD_FORMAT_TRACK)) {
   1.246 +					// Set Write Protect bit and bail.
   1.247 +					ctx->status = 0x40;
   1.248 +
   1.249 +					// Set IRQ only if IRQL has been cleared (no pending IRQs)
   1.250 +					ctx->irqe = !ctx->irql;
   1.251 +					ctx->irql = true;
   1.252 +
   1.253 +					return;
   1.254 +				}
   1.255 +			}
   1.256 +
   1.257 +			// Disc is ready to go. Parse the command word.
   1.258 +			switch (cmd) {
   1.259 +				case CMD_READ_ADDRESS:
   1.260 +					// Read Address
   1.261 +
   1.262 +					// reset data pointers
   1.263 +					ctx->data_pos = ctx->data_len = 0;
   1.264 +
   1.265 +					// load data buffer
   1.266 +					ctx->data[ctx->data_len++] = ctx->track;
   1.267 +					ctx->data[ctx->data_len++] = ctx->head;
   1.268 +					ctx->data[ctx->data_len++] = ctx->sector;
   1.269 +					switch (ctx->geom_secsz) {
   1.270 +						case 128:	ctx->data[ctx->data_len++] = 0; break;
   1.271 +						case 256:	ctx->data[ctx->data_len++] = 1; break;
   1.272 +						case 512:	ctx->data[ctx->data_len++] = 2; break;
   1.273 +						case 1024:	ctx->data[ctx->data_len++] = 3; break;
   1.274 +						default:	ctx->data[ctx->data_len++] = 0xFF; break;	// TODO: deal with invalid values better
   1.275 +					}
   1.276 +					ctx->data[ctx->data_len++] = 0;	// TODO: IDAM CRC!
   1.277 +					ctx->data[ctx->data_len++] = 0;
   1.278 +
   1.279 +					// B6, B5 = 0
   1.280 +					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   1.281 +					// B3 = CRC Error. Not possible.
   1.282 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   1.283 +					// B1 = DRQ. Data request.
   1.284 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   1.285 +					break;
   1.286 +
   1.287 +				case CMD_READ_SECTOR:
   1.288 +				case CMD_READ_SECTOR_MULTI:
   1.289 +					// Read Sector or Read Sector Multiple
   1.290 +					// TODO: multiple is not implemented!
   1.291 +					if (cmd == CMD_READ_SECTOR_MULTI) printf("WD279X: NOTIMP: read-multi\n");
   1.292 +
   1.293 +					// reset data pointers
   1.294 +					ctx->data_pos = ctx->data_len = 0;
   1.295 +
   1.296 +					// Calculate the LBA address of the required sector
   1.297 +					lba = ((((ctx->track * ctx->geom_heads) + ctx->head) * ctx->geom_spt) + ctx->sector - 1) * ctx->geom_secsz;
   1.298 +
   1.299 +					// Read the sector from the file
   1.300 +					fseek(ctx->disc_image, lba, SEEK_SET);
   1.301 +					ctx->data_len = fread(ctx->data, 1, ctx->geom_secsz, ctx->disc_image);
   1.302 +					// TODO: if datalen < secsz, BAIL! (call it a crc error or secnotfound maybe? also log to stderr)
   1.303 +					// TODO: if we're asked to do a Read Multi, malloc for an entire track, then just read a full track...
   1.304 +
   1.305 +					// B6 = 0
   1.306 +					// B5 = Record Type -- 1 = deleted, 0 = normal. We can't emulate anything but normal data blocks.
   1.307 +					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   1.308 +					// B3 = CRC Error. Not possible.
   1.309 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   1.310 +					// B1 = DRQ. Data request.
   1.311 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   1.312 +					break;
   1.313 +
   1.314 +				case CMD_READ_TRACK:
   1.315 +					// Read Track
   1.316 +					// B6, B5, B4, B3 = 0
   1.317 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   1.318 +					// B1 = DRQ. Data request.
   1.319 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   1.320 +					break;
   1.321 +
   1.322 +				case CMD_WRITE_SECTOR:
   1.323 +				case CMD_WRITE_SECTOR_MULTI:
   1.324 +					// Write Sector or Write Sector Multiple
   1.325 +
   1.326 +					// reset data pointers
   1.327 +					ctx->data_pos = ctx->data_len = 0;
   1.328 +
   1.329 +					// TODO: set "write pending" flag, and write LBA, and go from there.
   1.330 +
   1.331 +					// B6 = Write Protect. FIXME -- emulate this!
   1.332 +					// B5 = 0
   1.333 +					// B4 = Record Not Found. We're not going to see this... FIXME-not emulated
   1.334 +					// B3 = CRC Error. Not possible.
   1.335 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   1.336 +					// B1 = DRQ. Data request.
   1.337 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   1.338 +					break;
   1.339 +
   1.340 +				case CMD_FORMAT_TRACK:
   1.341 +					// Write Track (aka Format Track)
   1.342 +					// B6 = Write Protect. FIXME -- emulate this!
   1.343 +					// B5, B4, B3 = 0
   1.344 +					// B2 = Lost Data. Caused if DRQ isn't serviced in time. FIXME-not emulated
   1.345 +					// B1 = DRQ. Data request.
   1.346 +					ctx->status |= (ctx->data_pos < ctx->data_len) ? 0x02 : 0x00;
   1.347 +					break;
   1.348 +
   1.349 +				case CMD_FORCE_INTERRUPT:
   1.350 +					// Force Interrupt...
   1.351 +					// TODO: terminate current R/W op
   1.352 +					// TODO: variants for this command
   1.353 +					break;
   1.354 +			}
   1.355 +			break;
   1.356 +
   1.357 +		case WD279X_REG_TRACK:		// Track register
   1.358 +			ctx->track = val;
   1.359 +			break;
   1.360 +
   1.361 +		case WD279X_REG_SECTOR:		// Sector register
   1.362 +			ctx->sector = val;
   1.363 +			break;
   1.364 +
   1.365 +		case WD279X_REG_DATA:		// Data register
   1.366 +			// Save the value written into the data register
   1.367 +			ctx->data_reg = val;
   1.368 +
   1.369 +			// If we're processing a write command, and there's space in the
   1.370 +			// buffer, allow the write.
   1.371 +			if (ctx->data_pos < ctx->data_len) {
   1.372 +				// store data byte and increment pointer
   1.373 +				ctx->data[ctx->data_pos++] = val;
   1.374 +			}
   1.375 +			break;
   1.376 +	}
   1.377 +}
   1.378 +