// This module is routines for writing a disk drive.
// drive_write_disk writes the disk
//
// The drive must be at track 0 on startup or drive_seek_track0 called.
//
// Copyright 2021 David Gesswein.
// This file is part of MFM disk utilities.
//
// MFM disk utilities is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MFM disk utilities is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with MFM disk utilities. If not, see .
//
// 09/07/21 DJG Handle error from write track
// 03/07/21 DJG Only pad end if non zero
// 06/30/17 DJG Use emulator file number of heads, not command line.
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "msg.h"
#include "crc_ecc.h"
#include "emu_tran_file.h"
#include "mfm_decoder.h"
#include "cmd.h"
#include "cmd_write.h"
#include "deltas_read.h"
#include "pru_setup.h"
#include "drive.h"
#include "board.h"
static void generate_pwm_table(DRIVE_PARAMS *drive_params, int do_precomp);
// Write the disk.
//
// drive_params: Drive parameters
// deltas: Memory array containing the raw MFM delta time transition data
void drive_write_disk(DRIVE_PARAMS *drive_params)
{
// Loop variable
int cyl, head;
int track_size, cyl_size;
// Buffer for the drive data
uint8_t *data;
track_size = drive_params->emu_file_info->track_data_size_bytes +
drive_params->emu_file_info->track_header_size_bytes;
cyl_size = track_size * drive_params->emu_file_info->num_head;
data = msg_malloc(cyl_size,"Write data buffer");
generate_pwm_table(drive_params, 0);
for (cyl = 0; cyl < drive_params->num_cyl; cyl++) {
if (cyl >= drive_params->write_precomp_cyl) {
generate_pwm_table(drive_params, 1);
}
if (cyl != 0) {
// Slow seek doesn't slow down writing so always use it.
drive_step(DRIVE_STEP_SLOW, 1, DRIVE_STEP_UPDATE_CYL,
DRIVE_STEP_FATAL_ERR);
}
if (cyl % 5 == 0)
msg(MSG_PROGRESS, "At cyl %d\r", cyl);
emu_file_read_cyl(drive_params->emu_fd,
drive_params->emu_file_info, cyl, data, cyl_size);
//TODO need to handle this better either in emulator or here.
// May not be needed now that files are no longer padded with zeros.
{ int n;
for (n = 0; n < drive_params->emu_file_info->num_head; n++) {
int *d = (int *) data;
int index = track_size/4 - 1 + n * track_size/4;
// No transitions at end of write will cause emulator to fail
if (d[index] == 0) {
d[index] = 0x55555555;
}
}
}
pru_write_mem(MEM_DDR, data, cyl_size, 0);
for (head = 0; head < drive_params->num_head; head++) {
drive_set_head(head);
pru_write_word(MEM_PRU1_DATA, PRU1_CUR_HEAD, head);
if (pru_exec_cmd(CMD_WRITE_TRACK, 0)) {
drive_print_drive_status(MSG_FATAL, drive_get_drive_status());
exit(1);
}
}
}
}
// Make a table which we index with the six MSB of the MFM bitstream
// to determine the next PWM word.
// The MFM bitstream is the MFM data and clock bits referred to as
// mfm encoded here:
// http://en.wikipedia.org/wiki/Modified_Frequency_Modulation
// All the words generated must have a minimum duration of 2 bit cells,
// nominally 40 clocks, 200 ns.
// For example bit pattern 10010[1] is sent as a pulse with period 60
// counts and a pulse with period 40 for 10 MHz data rate.
// If we find a 11 we drop the second bit since we can't generate it.
// It's bad MFM data so shouldn't cause problems. 01 we flag as an
// error since we should never see it unless something has gone
// wrong. Without changing the PWM polarity we can't generate a zero
// followed by a 1. We pick the bits we process to ensure that valid
// MFM data won't generate the patterns we can't encode. For example
// bit pattern 1000 we only process the first two (10) and leave the
// last two bits (00) for the next lookup. For valid MFM code we
// could generate the waveform for all four bits instead since the
// next bit should be a 1. I don't in case it is a zero.
//
// Bits 31-28 are how many bits to remove from data. This is the MFM
// clock and data bits we will shift off, not anything to do with the
// data bits encoded by the MFM encoding.
// The bits we remove were choosen to make sure that the left over
// bits aren't a pattern we can't encode.
// Bits 27-25 unused
// bit 24 is flag for illegal bit pattern found
// Bits 23-16 are duration of 1. 0 is no one. (PWM ACMP)
// Bits 15-8 are signed period adjustment to apply to next word for write
// precompensation in this word.
// Bits 7-0 is period to next bit time. (PWM APRD)
// Period values are one less than actual period generated.
// Drives construction of PRU1 bit lookup table
typedef struct {
// Number of bits we can represent in a single PWM word (2 or 3)
uint32_t bitcount;
// Value of first bit (0 or 1)
uint32_t leading_bit;
// Error flag (see below)
uint32_t error_flag;
} table_rec_t;
static table_rec_t bit_table[] = {
{ 2, 0, 0 }, // 00 0000
{ 3, 0, 0 }, // 00 0001
{ 2, 0, 0 }, // 00 0010
{ 2, 0, 0 }, // 00 0011
// If the bit shifting algorithm is working correctly, we should
// never see a pattern starting with '01'. If we do, then
// something is wrong and and error is flagged.
{ 2, 0, 1 }, // 00 0100 treated as 0000
{ 3, 0, 1 }, // 00 0101 treated as 0001
{ 2, 0, 1 }, // 00 0110 treated as 0010
{ 2, 0, 1 }, // 00 0111 treated as 0011
{ 2, 1, 0 }, // 00 1000
{ 3, 1, 0 }, // 00 1001
{ 2, 1, 0 }, // 00 1010
{ 2, 1, 0 }, // 00 1011
// The next four patterns are not valid MFM data and cannot be
// generated. We just drop the second 1.
{ 2, 1, 0 }, // 00 1100 treated as 1000
{ 3, 1, 0 }, // 00 1101 treated as 1001
{ 2, 1, 0 }, // 00 1110 treated as 1010
{ 2, 1, 0 } // 00 1111 treated as 1011
};
static void generate_pwm_table(DRIVE_PARAMS *drive_params, int do_precomp) {
int idx;
int pat;
EMU_FILE_INFO *curr_info = drive_params->emu_file_info;
int bit_period = lround(200e6 / curr_info->sample_rate_hz);
int early_precomp_clk = lround(drive_params->early_precomp_ns / 1e9 * 200e6);
int late_precomp_clk = lround(drive_params->late_precomp_ns / 1e9 * 200e6);
pru_write_word(MEM_PRU0_DATA, PRU0_DEFAULT_PULSE_WIDTH, bit_period*2-1);
for (idx = 0; idx < 64; idx++) {
table_rec_t rec = bit_table[(idx >> 2) & 0xf];
uint32_t bits = (rec.bitcount<<28) | (rec.error_flag<<24) | ((rec.leading_bit*bit_period)<<16) | ((rec.bitcount*bit_period)-1);
if (rec.leading_bit) {
// If generating 10 which will be followed be 100 then
// gnerate the second one early
if (do_precomp && (idx >> 1) == 0x14) {
bits -= early_precomp_clk;
bits |= (early_precomp_clk & 0xff) << 8;
}
} else {
pat = (idx << rec.bitcount) >> 3;
// If two more zeros followed by 101 then we delay the first one
if (do_precomp && pat == 0x5) {
bits += late_precomp_clk;
bits |= (-late_precomp_clk & 0xff) << 8;
}
}
pru_write_word(MEM_PRU1_DATA, PRU1_BIT_TABLE + idx * PRU_WORD_SIZE_BYTES,
bits);
}
}