/*
 *      Copyright (C) 1993 Bas Laarhoven.

This program 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 2, or (at your option)
any later version.

This program 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 this program; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

 $Source: /usr/src/distr/ftape-0.9.7/RCS/fdc-io.c,v $
 $Author: bas $
 *
 $Revision: 1.9 $
 $Date: 1993/12/04 11:52:41 $
 $State: Alpha $
 *
 *      This file contains the low-level floppy disk interface code
 *      for the QIC-40/80 tape streamer device driver.
 */

static char RCSid[] = "$Id: fdc-io.c,v 1.9 1993/12/04 11:52:41 bas Alpha $";

#include <linux/errno.h>
#include <linux/sched.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/dma.h>

#include "fdc-io.h"
#include "ftape-io.h"
#include "ftape-rw.h"
#include "calibr.h"

/*      Global vars.
 */
int ftape_unit = -1;
int ftape_motor = 0;
int current_cylinder = -1;
int expect_stray_interrupt = 0;
int seek_completed = 0;
int interrupt_seen = 0;

/*      Local vars.
 */
int fdc_calibr_count;
int fdc_calibr_time;

#undef CHECK_TIMING

#ifdef CHECK_TIMING
static struct timeval t1 = { 0, 0 }, t2 = { 0, 0 };
static int delta_t;
static int new_command;
#endif

#define abs(a) ((a) < 0 ? -(a) : (a))

/*  Wait during a timeout period for the FDC to become ready.
 *  (either for input or output).
 *  Returns ETIME on timeout. 
 */
int
fdc_wait( int usecs, unsigned char mask, unsigned char state)
{
  int status;

  usecs = 1 + (fdc_calibr_count * usecs - 1) / fdc_calibr_time;
  while (usecs-- > 0) {
    status = inb_p( FDC_STATUS);
    if ((status & mask) == state) {
      return 0;
    }
  }
  return -ETIME;
}

int fdc_ready_wait( int usecs)
{
  return fdc_wait( usecs, FDC_DATA_READY, FDC_DATA_READY);
}

static void
fdc_usec_wait( int usecs)
{
  fdc_wait( usecs, 0, 1);       /* will always timeout ! */
}

int
fdc_wait_calibrate( void)
{
  return calibrate( "fdc_wait", fdc_usec_wait, &fdc_calibr_count, &fdc_calibr_time);
}

/*  Wait for a (short) while for the FDC to become ready
 *  and transfer the next command byte.
 *  Return -ETIME on timeout on getting ready (depends on hardware!).
 */
int
fdc_write( unsigned char data)
{
  if (fdc_wait( 1000, FDC_DATA_READY | FDC_DATA_OUTPUT, FDC_DATA_READY) < 0) {
    TRACE( 1, "fdc_write", "timeout");
    return -ETIME;
  } else {
    outb( data, FDC_DATA);
    fdc_usec_wait( 12);
    return 0;
  }
}

/*  Wait for a (short) while for the FDC to become ready
 *  and transfer the next result byte.
 *  Return -ETIME on timeout on getting ready (depends on hardware!).
 */
int
fdc_read( unsigned char* data)
{
  if (fdc_wait( 1000, FDC_DATA_READY | FDC_DATA_OUTPUT, FDC_DATA_READY | FDC_DATA_OUTPUT) < 0) {
    TRACE( 1, "fdc_write", "timeout");
    return -ETIME;
  } else {
    *data = inb( FDC_DATA);
    fdc_usec_wait( 12);
    return 0;
  }
}

/*  Output a cmd_len long command string to the FDC.
 *  The FDC should be ready to receive a new command or
 *  an error (EBUSY) will occur.
 */
int
fdc_command( unsigned char* cmd_data, int cmd_len)
{
  int status;
  int result = 0;
  unsigned long flags;

  save_flags( flags);
  cli();
  status = inb_p( FDC_STATUS);
  if (status & FDC_DATA_READY) {
    fdc_mode = *cmd_data;
    interrupt_seen = 0;
    while (cmd_len--) {
      result = fdc_write( *cmd_data++);
      if (result < 0) {
        TRACE( 0, "fdc_command", "aborted");
        /* recover ??? */
        break;
      }
    }
  } else {
    TRACE( 0, "fdc_command", "not ready");
    result = -EBUSY;
  }
  restore_flags( flags);
  return result;
}

/*  Input a res_len long result string from the FDC.
 *  The FDC should be ready to send the result or an error
 *  (EBUSY) will occur.
 */
int
fdc_result( unsigned char* res_data, int res_len)
{
  int status;
  int result = 0;
  unsigned long flags;

  save_flags( flags);
  cli();
  status = inb_p( FDC_STATUS);
  if (status & FDC_DATA_READY) {
    while (res_len--) {
      result = fdc_read( res_data++);
      if (result < 0) {
        TRACE( 0, "fdc_result", "aborted");
        /* recover ??? */
        break;
      }
    }
  } else {
    TRACE( 0, "fdc_result", "not ready");
    result = -EBUSY;
  }
  restore_flags( flags);
  return result;
}

/*      Handle command and result phases for
 *      commands without data phase.
 */
int
fdc_issue_command( unsigned char *out_data, int out_count,
                  unsigned char *in_data, int in_count)
{
  int result;

  if (out_count > 0) {
    result = fdc_command( out_data, out_count);
    if (result < 0) {
      TRACE( 0, "fdc_issue_command", "fdc_command failed");
      return result;
    }
  }
  /* will take 24 - 30 usec for fdc_sense_drive_status and
   * fdc_sense_interrupt_status commands.
   *    35 fails sometimes (5/9/93 SJL)
   */
  result = fdc_ready_wait( 100 /* usec */);
  if (result < 0) {
    TRACE( 0, "fdc_issue_command", "fdc_ready_wait failed");
    return result;
  }
  if (in_count > 0) {
    result = fdc_result( in_data, in_count);
    if (result < 0) {
      TRACE( 0, "fdc_issue_command", "result phase aborted");
      return result;
    }
  }
  return 0;
}

/*      FDC interrupt service routine.
 */
void
fdc_interrupt( void)
{
  int result;
  int status;
  int crc_error = 0;
  static unsigned int last_error_id = (unsigned) -1;
  static int soft_error_retry_count = 0;

  do_floppy = fdc_interrupt; /* hook our handler in fdc code */

#ifdef CHECK_TIMING
  do_gettimeofday( &t2);
  /* hack to get around un-initialized t1 on unexpected interrupt */
  if ((t1.tv_usec | t1.tv_sec) == 0) {
    t1 = t2;                    /* will give warning! */
  }
  /* end of hack */
  delta_t = (t2.tv_sec - t1.tv_sec) * 1000000 + (t2.tv_usec - t1.tv_usec);
  if (new_command) {
    if ((delta_t < 2000 * new_command && tracing > 1) || delta_t < 0) {
      TRACEi( -1, "tape_isr", "Fast interrupt for command:", new_command);
      TRACEi( -1, "tape_isr", "Lapsed time command to interrupt (usec):", delta_t);
    }
    new_command = 0;
  } else {
    if (tracing > 4 || delta_t < 0) {
      TRACEi( -1, "tape_isr", "Time to next interrupt (usec):", delta_t);
    }
  }
  t1 = t2;
#endif

  status = inb_p( FDC_STATUS);

  if (status & FDC_BUSY) {
    /*        Entering Result Phase
     */
    if (status & (FDC_DATA_READY | FDC_DATA_OUTPUT) != (FDC_DATA_READY | FDC_DATA_OUTPUT)) {
      TRACE( 0, "tape_isr", "fdc in wrong state");
    }
    result = fdc_result( global_in, 7); /* better get it quick ! */

    if (fdc_mode == fdc_idle) {
      TRACE( 0, "tape_isr", "Warning: unexpected irq during fdc_idle mode");
    } else if (runner_status != aborting) {

      if (result < 0) {
        /*      Entered unknown state...
         */
        TRACE( 0, "tape_isr", "error during FDC Result Phase");
      } else if (runner_status != do_abort) {
        /*      Normal command completion, check status.
         */
        switch (fdc_mode) {
        case fdc_reading_data: {
          TRACEi( 4, "tape_isr", "fdc irq reading segment", buffer[ head].segment_id);
          if ((global_in[ 0] & ST0_INT_MASK) != FDC_INT_NORMAL) {
            /* abnormal command completion: crc error ?
             * also caused by missing address mark !
             */
            TRACE( 4, "tape_isr", "abnormal command completion");
            if (tracing > 8) {
              printk( "-- Status = [0]=0x%02x,[1]=0x%02x,[2]=0x%02x\n",
                     global_in[ 0], global_in[ 1], global_in[ 2]);
              printk( "-- Cyl.=0x%02x, Head=0x%02x, Sect.=0x%02x, Size=0x%02x\n",
                     global_in[ 3], global_in[ 4], global_in[ 5], global_in[ 6]);
            }
          }
          if ((global_in[ 0] & ST0_INT_MASK) == FDC_INT_ABNORMAL) {
            if ((global_in[ 1] & 0x24) && bad_sector_map == ((unsigned) EMPTY_SEGMENT) >> 1) {
              /*   This condition occurs when reading a `fake' sector
               *   that's not accessible. Doesn't matter as we would
               *   have ignored it anyway.
               */
              TRACE( 4, "tape_isr", "skipping empty segment (read)");
            } else {
              /* abnormal command completion: crc error ?
               * also caused by missing address mark !
               */
              int dma_residue = get_dma_residue( FLOPPY_DMA);
              disable_dma( FLOPPY_DMA);
              if (dma_residue & (SECTOR_SIZE - 1)) {
                TRACE( 0, "tape_isr", "dma residue not multiple of sector size");
                fdc_mode = fdc_idle;
                break;
              }
              if (tracing > 8) {
                printk( "-- DMA residue = 0x%04x\n", dma_residue);
              }
              if (global_in[ 1] & 0x04) {
                /*  Sector not found ! This is serious: can't rely
                 *  on ecc code, so abort and retry.
                 */
                crc_error = 0;
              } else {
                if (((global_in[ 1] | global_in[ 2]) & 0x21) == 0) {
                  /*  Not a crc error or missing AM error, give a notice
                   *  and handle just as if it were a crc error.
                   */
                  TRACE( 8, "tape_isr", "unexpected error, see state above");
                }
                crc_error = 1;
                if (buffer[ head].segment_id == last_error_id) {
                  ++soft_error_retry_count;
                } else {        /* first time around */
                  last_error_id = buffer[ head].segment_id;
                  soft_error_retry_count = 1;
                }
              }
              if (crc_error && soft_error_retry_count > 0) {
                int nr_not_read = dma_residue / SECTOR_SIZE;
                /*    Crc error in data field, let ecc handle this
                 *    as it is much faster than a physical retry.
                 *    Adjust variables to skip bad sector.
                 */
                TRACEi( 0, "tape_isr", "CRC error in segment",
                       buffer[ head].segment_id);
                if (nr_not_read > 0) {
                  sector_count-= nr_not_read - 1;
                  bad_sector_map <<= nr_not_read - 1; /* re-adjust */
                } else {
                  /* figure out what to do else, as I don't
                     know yet of a reason why this can happen */
                }
              } else {
                /*    Should retry on all other errors.
                 */
                if (crc_error) {
                  TRACEi( 1, "tape_isr", "SOFT(?) crc error in segment",
                         buffer[ head].segment_id);
                }
                runner_status = aborting;
                fdc_mode = fdc_idle;
                buffer[ head].status = error;
                TRACE( 0, "tape_isr", "read error, forcing retry");
                break;
              }
            }
          }
          if (runner_status != running) {
            /*  We've got a problem: what's happening ?
             */
            TRACEi( 0, "tape_isr", "fdc_reading_data in illegal runner state",
                   runner_status);
          }
          if (runner_status == running && buffer[ head].status == reading) {

            /*  Update var's influenced by previous DMA operation.
             */
            sector_offset += sector_count;
            buffer_ptr += sector_count * 1024;
            remaining -= sector_count;
            
            if (crc_error) {
              /* bit0 [1] : missing id AM or data AM
               * bit2 [1] : sector not found
               * bit5 [1] : crc error in id or data field
               * bit0 [2] : missing data AM
               * bit5 [2] : crc error in data field
               */
              /* merge position of bad sector in bitmap */
              if ((global_in[ 2] & 0x20) != 0) {
                buffer[ head].crc_error_map |=  (3 << (sector_offset - 2));
                history.CRC_errors++;
              } else {
                buffer[ head].crc_error_map |=  (1 << (sector_offset - 1));
                history.AM_errors++; /* crc error in id too */
              }
            }
            if (remaining > 0 && calc_next_cluster() > 0) {

              /*        Read remaining sectors in segment
               */
              if (setup_fdc_and_dma( FDC_READ) != 0) {

                /*      On error abort read operation but do updata buffer information.
                 */
                buffer[ head].bytes = (char*) buffer_ptr - (char*) buffer[ head].address;
                buffer[ head].status = error;
                next_buffer( &head);
                runner_status = aborting;
              } else {
                break;          /* keep fdc_mode set to fdc_reading_data */
              }
            } else {
              /*        Read next segment after updating buffer information.
               */
              buffer[ head].bytes = (char*) buffer_ptr - (char*) buffer[ head].address;
              buffer[ head].status = full; /* segment done */
              next_buffer( &head);

              /*        Read ahead: read next segment if possible
               */
              if ((next_segment % segments_per_track) != 0) {
                if (buffer[ head].status == empty) {
                  setup_new_segment( head, next_segment, 0, sectors_per_segment);
                  if (calc_next_cluster() == 0 || setup_fdc_and_dma( FDC_READ) != 0) {
                    TRACE( 0, "tape_isr", "couldn't start read-ahead");
                    runner_status = do_abort;
                  } else {
                    buffer[ head].status = reading;
                    break;      /* keep fdc_mode set to fdc_reading_data */
                  }
                } else {
                  TRACE( 4, "tape_isr", "data buffer overrun");
                  runner_status = buffer_overrun;
                }
              } else {
                /*      Just got last segment on track
                 */
                runner_status = logical_eot;
              }
            }
          }
          fdc_mode = fdc_idle;
          break;
        }
        case fdc_reading_id: {
          fdc_mode = fdc_idle;
          break;
        }
        case fdc_writing_data: {
          TRACEi( 4, "tape_isr", "fdc irq writing segment", buffer[ head].segment_id);
          if (tracing > 12) {
            int i;
            for (i = 0; i < NR_ITEMS( buffer); ++i) {
              printk( "buffer[%d].status = %d, ", i, buffer[i].status);
              printk( "buffer[%d].segment_id = %d\n", i, buffer[i].segment_id);
            }
          }
          if ((global_in[ 0] & ST0_INT_MASK) != FDC_INT_NORMAL) {
            TRACE( 4, "tape_isr", "abnormal command completion");
            if (tracing > 8) {
              printk( "-- Status = [0]=0x%02x,[1]=0x%02x,[2]=0x%02x\n",
                     global_in[ 0], global_in[ 1], global_in[ 2]);
              printk( "-- Cyl.=0x%02x, Head=0x%02x, Sect.=0x%02x, Size=0x%02x\n",
                     global_in[ 3], global_in[ 4], global_in[ 5], global_in[ 6]);
            }
          }
          if ((global_in[ 0] & ST0_INT_MASK) == FDC_INT_ABNORMAL &&
              (global_in[ 1] & 0x24) && bad_sector_map == ((unsigned) EMPTY_SEGMENT) >> 1) {
            /* ignore problem finding fake segment */
            TRACE( 4, "tape_isr", "skipping empty segment (write)");
          } else if ((global_in[ 0] & ST0_INT_MASK) != FDC_INT_NORMAL) {
            /* abnormal command completion: crc error ?
             * also caused by missing address mark !
             */
            int dma_residue = get_dma_residue( FLOPPY_DMA);
            disable_dma( FLOPPY_DMA);
            if (dma_residue & (SECTOR_SIZE - 1)) {
              TRACE( 0, "tape_isr", "dma residue not multiple of sector size");
              fdc_mode = fdc_idle;
              break;
            }
            if (tracing > 8) {
              printk( "-- DMA residue = 0x%04x\n", dma_residue);
            }
            /* handle crc errors in address field */
            if (global_in[ 1] & 0x25) {
              int nr_not_written = dma_residue / SECTOR_SIZE;
              /*  if writing empty sector, ignore this error.
               */
              /* adjust for incomplete dma operation, retry failing sector */
              sector_count-= nr_not_written;
              bad_sector_map <<= nr_not_written; /* re-adjust */
              buffer[ head].status = error;
            } else {
              TRACE( -1, "tape_isr", "unforseen write error");
            }
            runner_status = aborting;
            fdc_mode = fdc_idle;
            break;
          }
          if (runner_status != running) {
            /*  We've got a problem: what's happening ?
             */
            TRACEi( 0, "tape_isr", "fdc_writing_data in illegal runner state",
                   runner_status);
          }
          if (runner_status == running && buffer[ head].status == writing) {
            /*  Update var's influenced by previous DMA operation.
             */
            sector_offset += sector_count;
            buffer_ptr += sector_count * 1024;
            remaining -= sector_count;
            
            if (remaining > 0 && calc_next_cluster() > 0) {
              /*        Write remaining sectors in segment
               */
              if (setup_fdc_and_dma( FDC_WRITE) != 0) {

                /*      On error abort write operation but do updata buffer information.
                 */
                buffer[ head].status = error;
                next_buffer( &head);
                runner_status = aborting;
              } else {
                break;          /* keep fdc_mode set to fdc_writing_data */
              }
            } else {
              /*        Write next segment after updating buffer information.
               */
              buffer[ head].bytes = (char*) buffer_ptr - (char*) buffer[ head].address;
              buffer[ head].status = empty; /* segment done */
              next_buffer( &head);

              if ((next_segment % segments_per_track) != 0) {
                if (buffer[ head].status == full) {
                  setup_new_segment( head, next_segment, 0, sectors_per_segment);
                  if (calc_next_cluster() == 0 || setup_fdc_and_dma( FDC_WRITE) != 0) {
                    TRACE( 1, "tape_isr", "couldn't start next write");
                    runner_status = do_abort;
                  } else {
                    buffer[ head].status = writing;
                    break;      /* keep fdc_mode set to fdc_writing_data */
                  }
                } else {
                  TRACE( 4, "tape_isr", "data buffer underrun");
                  runner_status = buffer_underrun;
                }
              } else {
                /*      Just got last segment on track
                 */
                runner_status = logical_eot;
              }
            }
          } else {
            if (runner_status != running) {
              TRACE( 0, "tape_isr", "runner-status != running");
            }
            if (buffer[ head].status != writing) {
              TRACE( 0, "tape_isr", "buffer-status != writing");
            }
          }
          fdc_mode = fdc_idle;
          break;
        }
        default: {
          TRACEi( 0, "tape_isr", "Warning: unexpected fdc_mode:", fdc_mode);
          fdc_mode = fdc_idle;
          break;
        }
        }
      }
      if (runner_status == do_abort) {
        /*      cease operation, remember tape position
         */
        TRACE( 0, "tape_isr", "runner aborting");
        runner_status = aborting;
        expect_stray_interrupt = 1;
      }
    }
  } else {
    /*  clear interrupt, cause should be gotten by issueing
     *  a Sense Interrupt Status command.
     */
    if (fdc_mode == fdc_recalibrating || fdc_mode == fdc_seeking) {
      seek_completed = 1;
      fdc_mode = fdc_idle;
    } else if (!wait_intr) {
      if (!expect_stray_interrupt) {
        TRACE( 0, "tape_isr", "unexpected stray interrupt");
      } else {
        TRACE( 4, "tape_isr", "expected stray interrupt");
      }
    }
  }

  /*    Handle sleep code.
   */
  ++interrupt_seen;
  if (wait_intr) {
    wake_up_interruptible( &wait_intr);
  }
}

/*      Wait for FDC interrupt with timeout.
 */
int
fdc_interrupt_wait( int time) {
  
  unsigned long flags;
  struct wait_queue wait = { current, NULL };
  int result = -ETIME;
  
  if (wait_intr) {
    TRACE( 0, "fdc_interrupt_wait", "error: nested call");
    return -EIO;                /* return error... */
  }
  save_flags( flags);
  cli();
  if (interrupt_seen == 0) {
    int signal_pending = 0;
   /*   timeout time will be between 0 and MSPT milliseconds too long ! */
    current->timeout = jiffies + 1 + (time + MSPT - 1) / MSPT;
    current->state = TASK_INTERRUPTIBLE;
  
    add_wait_queue( &wait_intr, &wait);
  
    while (1) {
      sti();                    /* allow interrupt_seen */
      while (!interrupt_seen && current->state != TASK_RUNNING) {
        schedule();             /* sets TASK_RUNNING on timeout */
      }
      cli();                    /* prevents late interrupt_seen */
      if (current->signal & ~current->blocked) {
        /* probably awakened by non-blocked signal, fdc probably still busy.
         * will create a mess if we don't reset it !
         */
#if 0
        disable_dma( FLOPPY_DMA);
        fdc_reset();
#endif
        /* hack (?) to allow gracefull exit
         * just mask every signal that interferes
         */
        current->blocked |= (current->signal & ~current->blocked) ;
        signal_pending = 1;
      } else {
        if (signal_pending) {
          TRACE( 0, "fdc_interrupt_wait", "error: aborted on signal");
          current->timeout = 0;
          remove_wait_queue( &wait_intr, &wait);
          restore_flags( flags);
          return -EINTR;
        } else {
          break;
        }
      }
    }
    /* get here only on timeout or qic117 interrupt, TASK_RUNNING */
    if (interrupt_seen) {
      current->timeout = 0;     /* interrupt hasn't cleared this */
      result = 0;
    }
    remove_wait_queue( &wait_intr, &wait);
  } else {
    result = 0;
  }
  restore_flags( flags);
  interrupt_seen = 0;		/* should be somewhere else ! */
  return result;
}

/*      Start/stop drive motor. Enable DMA mode.
 */
void
fdc_motor( int motor)
{
  int data;

  if (motor) {
    TRACE( 4, "fdc_motor", "turning motor on");
  } else {
    TRACE( 4, "fdc_motor", "turning motor off");
  }
  ftape_motor = motor;
  data = ftape_unit | FDC_RESET_NOT | FDC_DMA_MODE;
  if (motor) {
    data |= FDC_MOTOR_0 << ftape_unit;
  }
  outb_p( data, FDC_CONTROL);
  ftape_sleep( 10 * MILLISECOND);
}

/*      Reset the floppy disk controller. Leave the FDTAPE unit selected.
 */
void
fdc_reset(void)
{
  int control_word;
  expect_stray_interrupt = 1;

  control_word = ftape_unit | FDC_DMA_MODE | (ftape_motor ? FDC_MOTOR_0 << ftape_unit : 0);

  /* Assert the reset line.  Leave the proper unit selected. */
  outb_p( control_word, FDC_CONTROL);
  ftape_sleep( MILLISECOND);    /* > 14 fdc clocks */

  /* Now release the reset line. */
  control_word |= FDC_RESET_NOT;

  outb_p( control_word, FDC_CONTROL);
  ftape_sleep( 10 * MILLISECOND); /* ??? */

  /* Select clock for fdc.  May need to be changed to select data rate. */
  outb_p( 0, FDC_VFO);          /* set high density (500 Kbps) mode */

  expect_stray_interrupt = 0;
}

/* When we're done, put the fdc into reset mode so that the regular
   floppy disk driver will figure out that something is wrong and
   initialize the controller the way it wants. */
void
fdc_disable( void)
{
  outb_p( ftape_unit, FDC_CONTROL);
}

/*      Specify FDC seek-rate
 */
int
fdc_set_seek_rate( int seek_rate)
{
  unsigned char in[ 3];

  in[ 0] = FDC_SPECIFY;
  in[ 1] = ((16 - seek_rate) << 4) | 13 /* head_unload_time */;
  in[ 2] = (1 /* head_load_time */ << 1) | 0 /* non_dma */;

  return fdc_command( in, 3);
}

/*      Sense drive status: get unit's drive status (ST3)
 */
int
fdc_sense_drive_status( int *st3)
{
  int result;
  unsigned char out[ 2];
  unsigned char in[ 1];

  out[ 0] = FDC_SENSED;
  out[ 1] = ftape_unit;
  result = fdc_issue_command( out, 2, in, 1);
  if (result < 0) {
    TRACE( 0, "fdc_sense_drive_status", "issue_command failed");
    return result;
  } else {
    *st3 = in[ 0];
    return 0;
  }
}

/*      Sense Interrupt Status command:
 *      should be issued at the end of each seek.
 *      get ST0 and current cylinder.
 */
int
fdc_sense_interrupt_status( int *st0, int *current_cylinder)
{
  int result;
  unsigned char out[ 1];
  unsigned char in[ 2];

  out[ 0] = FDC_SENSEI;
  result = fdc_issue_command( out, 1, in, 2);
  if (result) {
    TRACE( 0, "fdc_sense_interrupt_status", "issue_command failed");
    return result;
  } else {
    *st0 = in[ 0];
    *current_cylinder = in[ 1];
    return 0;
    }
}

/*      step to track
 */
int
fdc_seek( int track)
{
  int result;
  unsigned char out[3];
  int st0, pcn;

  ftape_sleep( MILLISECOND);    /* NEEDED to prevent problems */

  out[ 0] = FDC_SEEK;
  out[ 1] = ftape_unit;
  out[ 2] = track;
  seek_completed = 0;
  result = fdc_command( out, 3);
#ifdef CHECK_TIMING
  do_gettimeofday( &t1);
  new_command = abs( track - current_cylinder);
#endif
  if (result != 0) {
    TRACEi( 0, "fdc_seek", "failed, status =", result);
    if (tracing > 8) {
      printk( "-- destination was: %d\n", track);
      printk( "-- resetting FDC...\n");
    }
    /*  We really need this command to work !
     */
    fdc_reset();
    fdc_set_seek_rate( 2);
    return result;
  }

  /*    Handle interrupts until seek_completed or timeout.
   */
  for (;;) {
    result = fdc_interrupt_wait( 2 * SECOND);
    if (result < 0) {
      TRACEi( 1, "fdc_seek", "fdc_interrupt_wait timeout, status =", result);
      return result;
    } else if (seek_completed) {
      result = fdc_sense_interrupt_status( &st0, &pcn);
      if (result != 0) {
        TRACEi( 0, "fdc_seek", "fdc_sense_interrupt_status failed, status =", result);
        return result;
      }
      if ((st0 & ST0_SEEK_END) == 0) {
        TRACE( 0, "fdc_seek", "no seek-end after seek completion !??");
        return -EIO;
      }
      break;
    }
  }
  /*    Verify whether we issued the right tape command.
   */
  /* Verify that we seek to the proper track. */
  if (pcn != track) {
    TRACE( 1, "fdc_seek", "bad seek..");
    return -EIO;
  }
  current_cylinder = pcn;
  return 0;
}

/*      Recalibrate and wait until home.
 */
int
fdc_recalibrate( void)
{
  int result;
  unsigned char out[ 2];
  int st0;
  int pcn;
  int retry;

  TRACE( 4, "fdc_recalibrate", "called");

  result = fdc_set_seek_rate( 6);
  if (result) {
    TRACEi( 0, "fdc_recalibrate", "fdc_set_seek_rate failed, status =", result);
    return result;
  }
  out[ 0] = FDC_RECAL;
  out[ 1] = ftape_unit;
  seek_completed = 0;
  result = fdc_command( out, 2);
  if (result) {
    TRACEi( 0, "fdc_recalibrate", "fdc_command failed, status =", result);
    return result;
  }
  /*    Handle interrupts until seek_completed or timeout.
   */
  for (retry = 0;; ++retry) {
    result = fdc_interrupt_wait( 2 * SECOND);
    if (result < 0) {
      TRACE( 1, "fdc_recalibrate", "fdc_interrupt_wait failed");
      return result;
    } else if (result == 0 && seek_completed) {
      result = fdc_sense_interrupt_status( &st0, &pcn);
      if (result != 0) {
        TRACEi( 0, "fdc_recalibrate", "fdc_sense_interrupt_status failed, status =", result);
        return result;
      }
      if ((st0 & ST0_SEEK_END) == 0) {
        if (retry < 1) {
          continue;             /* some drives/fdc's give an extra interrupt */
        } else {
          TRACE( 0, "fdc_recalibrate", "no seek-end after seek completion !??");
          return -EIO;
        }
      }
      break;
    }
  }
  current_cylinder = pcn;
  if (pcn != 0) {
    TRACEi( 0, "fdc_recalibrate", "failed: resulting track =", pcn);
  }
  result = fdc_set_seek_rate( 2);
  if (result != 0) {
    TRACEi( 0, "fdc_recalibrate", "fdc_set_seek_rate failed, status =", result);
    return result;
  }

  return 0;
}

