276 lines
7 KiB
C++
276 lines
7 KiB
C++
/*
|
|
*
|
|
* Support for reading .SCP (Supercard Pro) disk flux dumps.
|
|
*
|
|
* By Keir Fraser in 2014.
|
|
*
|
|
* This file is free and unencumbered software released into the public domain.
|
|
* For more information, please refer to <http://unlicense.org/>
|
|
*/
|
|
|
|
#include "sysconfig.h"
|
|
#include "sysdeps.h"
|
|
|
|
#include "scp.h"
|
|
#include "zfile.h"
|
|
#include "gui.h"
|
|
#include "uae.h"
|
|
//#include "uae/endian.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#define MAX_REVS 5
|
|
|
|
enum pll_mode {
|
|
PLL_fixed_clock, /* Fixed clock, snap phase to flux transitions. */
|
|
PLL_variable_clock, /* Variable clock, snap phase to flux transitions. */
|
|
PLL_authentic /* Variable clock, do not snap phase to flux transition. */
|
|
};
|
|
|
|
struct scpdrive {
|
|
struct zfile *zf;
|
|
|
|
/* Current track number. */
|
|
unsigned int track;
|
|
|
|
/* Raw track data. */
|
|
uint16_t *dat;
|
|
unsigned int datsz;
|
|
|
|
unsigned int revs; /* stored disk revolutions */
|
|
unsigned int dat_idx; /* current index into dat[] */
|
|
unsigned int index_pos; /* next index offset */
|
|
unsigned int nr_index;
|
|
|
|
unsigned int index_off[MAX_REVS]; /* data offsets of each index */
|
|
|
|
/* Accumulated read latency in nanosecs. */
|
|
uint64_t latency;
|
|
|
|
/* Flux-based streams: Authentic emulation of FDC PLL behaviour? */
|
|
enum pll_mode pll_mode;
|
|
|
|
/* Flux-based streams. */
|
|
int flux; /* Nanoseconds to next flux reversal */
|
|
int clock, clock_centre; /* Clock base value in nanoseconds */
|
|
unsigned int clocked_zeros;
|
|
};
|
|
static struct scpdrive drive[4];
|
|
|
|
#define CLOCK_CENTRE 2000 /* 2000ns = 2us */
|
|
#define CLOCK_MAX_ADJ 10 /* +/- 10% adjustment */
|
|
#define CLOCK_MIN(_c) (((_c) * (100 - CLOCK_MAX_ADJ)) / 100)
|
|
#define CLOCK_MAX(_c) (((_c) * (100 + CLOCK_MAX_ADJ)) / 100)
|
|
|
|
#define SCK_NS_PER_TICK (25u)
|
|
|
|
int scp_open(struct zfile *zf, int drv, int *num_tracks)
|
|
{
|
|
struct scpdrive *d = &drive[drv];
|
|
uint8_t header[0x10] = { 0 };
|
|
|
|
scp_close(drv);
|
|
|
|
zfile_fread(header, sizeof(header), 1, zf);
|
|
|
|
if (memcmp(header, "SCP", 3) != 0) {
|
|
write_log(_T("SCP file header missing\n"));
|
|
return 0;
|
|
}
|
|
|
|
if (header[5] == 0) {
|
|
write_log(_T("SCP file has invalid revolution count (%u)\n"), header[5]);
|
|
return 0;
|
|
}
|
|
|
|
if (header[9] != 0 && header[9] != 16) {
|
|
write_log(_T("SCP file has unsupported bit cell time width (%u)\n"),
|
|
header[9]);
|
|
return 0;
|
|
}
|
|
|
|
d->zf = zf;
|
|
d->revs = min((int)header[5], MAX_REVS);
|
|
*num_tracks = header[7] + 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void scp_close(int drv)
|
|
{
|
|
struct scpdrive *d = &drive[drv];
|
|
if (!d->revs)
|
|
return;
|
|
xfree(d->dat);
|
|
memset(d, 0, sizeof(*d));
|
|
}
|
|
|
|
int scp_loadtrack(
|
|
uae_u16 *mfmbuf, uae_u16 *tracktiming, int drv,
|
|
int track, int *tracklength, int *multirev,
|
|
int *gapoffset, int *nextrev, bool setrev)
|
|
{
|
|
struct scpdrive *d = &drive[drv];
|
|
uint8_t trk_header[4];
|
|
uint32_t longwords[3];
|
|
unsigned int rev, trkoffset[MAX_REVS];
|
|
uint32_t hdr_offset, tdh_offset;
|
|
|
|
*multirev = 1;
|
|
*gapoffset = -1;
|
|
|
|
xfree(d->dat);
|
|
d->dat = NULL;
|
|
d->datsz = 0;
|
|
|
|
hdr_offset = 0x10 + track*sizeof(uint32_t);
|
|
|
|
zfile_fseek(d->zf, hdr_offset, SEEK_SET);
|
|
|
|
zfile_fread(longwords, sizeof(uint32_t), 1, d->zf);
|
|
tdh_offset = le32toh(longwords[0]);
|
|
|
|
zfile_fseek(d->zf, tdh_offset, SEEK_SET);
|
|
|
|
zfile_fread(trk_header, sizeof(trk_header), 1, d->zf);
|
|
if (memcmp(trk_header, "TRK", 3) != 0)
|
|
return 0;
|
|
|
|
if (trk_header[3] != track)
|
|
return 0;
|
|
|
|
for (rev = 0 ; rev < d->revs ; rev++) {
|
|
zfile_fread(longwords, sizeof(longwords), 1, d->zf);
|
|
trkoffset[rev] = tdh_offset + le32toh(longwords[2]);
|
|
d->index_off[rev] = le32toh(longwords[1]);
|
|
d->datsz += d->index_off[rev];
|
|
}
|
|
|
|
d->dat = xmalloc(uint16_t, d->datsz * sizeof(d->dat[0]));
|
|
d->datsz = 0;
|
|
|
|
for (rev = 0 ; rev < d->revs ; rev++) {
|
|
zfile_fseek(d->zf, trkoffset[rev], SEEK_SET);
|
|
zfile_fread(&d->dat[d->datsz],
|
|
d->index_off[rev] * sizeof(d->dat[0]), 1,
|
|
d->zf);
|
|
d->datsz += d->index_off[rev];
|
|
d->index_off[rev] = d->datsz;
|
|
}
|
|
|
|
d->track = track;
|
|
d->pll_mode = PLL_authentic;
|
|
d->dat_idx = 0;
|
|
d->index_pos = d->index_off[0];
|
|
d->clock = d->clock_centre = CLOCK_CENTRE;
|
|
d->nr_index = 0;
|
|
d->flux = 0;
|
|
d->clocked_zeros = 0;
|
|
|
|
scp_loadrevolution(mfmbuf, drv, tracktiming, tracklength);
|
|
return 1;
|
|
}
|
|
|
|
static int scp_next_flux(struct scpdrive *d)
|
|
{
|
|
uint32_t val = 0, flux, t;
|
|
|
|
for (;;) {
|
|
if (d->dat_idx >= d->index_pos) {
|
|
uint32_t rev = d->nr_index++ % d->revs;
|
|
d->index_pos = d->index_off[rev];
|
|
d->dat_idx = rev ? d->index_off[rev-1] : 0;
|
|
return -1;
|
|
}
|
|
|
|
t = be16toh(d->dat[d->dat_idx++]);
|
|
|
|
if (t == 0) { /* overflow */
|
|
val += 0x10000;
|
|
continue;
|
|
}
|
|
|
|
val += t;
|
|
break;
|
|
}
|
|
|
|
flux = val * SCK_NS_PER_TICK;
|
|
return (int)flux;
|
|
}
|
|
|
|
static int flux_next_bit(struct scpdrive *d)
|
|
{
|
|
int new_flux;
|
|
|
|
while (d->flux < (d->clock/2)) {
|
|
if ((new_flux = scp_next_flux(d)) == -1)
|
|
return -1;
|
|
d->flux += new_flux;
|
|
d->clocked_zeros = 0;
|
|
}
|
|
|
|
d->latency += d->clock;
|
|
d->flux -= d->clock;
|
|
|
|
if (d->flux >= (d->clock/2)) {
|
|
d->clocked_zeros++;
|
|
return 0;
|
|
}
|
|
|
|
if (d->pll_mode != PLL_fixed_clock) {
|
|
/* PLL: Adjust clock frequency according to phase mismatch. */
|
|
if ((d->clocked_zeros >= 1) && (d->clocked_zeros <= 3)) {
|
|
/* In sync: adjust base clock by 10% of phase mismatch. */
|
|
int diff = d->flux / (int)(d->clocked_zeros + 1);
|
|
d->clock += diff / 10;
|
|
} else {
|
|
/* Out of sync: adjust base clock towards centre. */
|
|
d->clock += (d->clock_centre - d->clock) / 10;
|
|
}
|
|
|
|
/* Clamp the clock's adjustment range. */
|
|
d->clock = max(CLOCK_MIN(d->clock_centre),
|
|
min(CLOCK_MAX(d->clock_centre), d->clock));
|
|
} else {
|
|
d->clock = d->clock_centre;
|
|
}
|
|
|
|
/* Authentic PLL: Do not snap the timing window to each flux transition. */
|
|
new_flux = (d->pll_mode == PLL_authentic) ? d->flux / 2 : 0;
|
|
d->latency += d->flux - new_flux;
|
|
d->flux = new_flux;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void scp_loadrevolution(
|
|
uae_u16 *mfmbuf, int drv, uae_u16 *tracktiming,
|
|
int *tracklength)
|
|
{
|
|
struct scpdrive *d = &drive[drv];
|
|
uint64_t prev_latency;
|
|
uint32_t av_latency;
|
|
unsigned int i, j;
|
|
int b;
|
|
|
|
d->latency = prev_latency = 0;
|
|
for (i = 0; (b = flux_next_bit(d)) != -1; i++) {
|
|
if ((i & 15) == 0)
|
|
mfmbuf[i>>4] = 0;
|
|
if (b)
|
|
mfmbuf[i>>4] |= 0x8000u >> (i&15);
|
|
if ((i & 7) == 7) {
|
|
tracktiming[i>>3] = d->latency - prev_latency;
|
|
prev_latency = d->latency;
|
|
}
|
|
}
|
|
|
|
if (i & 7)
|
|
tracktiming[i>>3] = ((d->latency - prev_latency) * 8) / (i & 7);
|
|
|
|
av_latency = prev_latency / (i>>3);
|
|
for (j = 0; j < (i+7)>>3; j++)
|
|
tracktiming[j] = ((uint32_t)tracktiming[j] * 1000u) / av_latency;
|
|
|
|
*tracklength = i;
|
|
}
|