blob: 2165324456520a5f786716154b63491519aaebb7 [file] [log] [blame]
/*
* Copyright (C) 1993-1996 Bas Laarhoven,
* (C) 1996-1997 Claus-Justus Heine.
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: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-io.c,v $
* $Revision: 1.7.4.2 $
* $Date: 1997/11/16 14:48:17 $
*
* This file contains the low-level floppy disk interface code
* for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for
* Linux.
*/
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/dma.h>
#include <asm/irq.h>
#include <linux/ftape.h>
#include <linux/qic117.h>
#include "../lowlevel/ftape-tracing.h"
#include "../lowlevel/fdc-io.h"
#include "../lowlevel/fdc-isr.h"
#include "../lowlevel/ftape-io.h"
#include "../lowlevel/ftape-rw.h"
#include "../lowlevel/ftape-ctl.h"
#include "../lowlevel/ftape-calibr.h"
#include "../lowlevel/fc-10.h"
/* Global vars.
*/
static int ftape_motor;
volatile int ftape_current_cylinder = -1;
volatile fdc_mode_enum fdc_mode = fdc_idle;
fdc_config_info fdc;
DECLARE_WAIT_QUEUE_HEAD(ftape_wait_intr);
unsigned int ft_fdc_base = CONFIG_FT_FDC_BASE;
unsigned int ft_fdc_irq = CONFIG_FT_FDC_IRQ;
unsigned int ft_fdc_dma = CONFIG_FT_FDC_DMA;
unsigned int ft_fdc_threshold = CONFIG_FT_FDC_THR; /* bytes */
unsigned int ft_fdc_rate_limit = CONFIG_FT_FDC_MAX_RATE; /* bits/sec */
int ft_probe_fc10 = CONFIG_FT_PROBE_FC10;
int ft_mach2 = CONFIG_FT_MACH2;
/* Local vars.
*/
static spinlock_t fdc_io_lock;
static unsigned int fdc_calibr_count;
static unsigned int fdc_calibr_time;
static int fdc_status;
volatile __u8 fdc_head; /* FDC head from sector id */
volatile __u8 fdc_cyl; /* FDC track from sector id */
volatile __u8 fdc_sect; /* FDC sector from sector id */
static int fdc_data_rate = 500; /* data rate (Kbps) */
static int fdc_rate_code; /* data rate code (0 == 500 Kbps) */
static int fdc_seek_rate = 2; /* step rate (msec) */
static void (*do_ftape) (void);
static int fdc_fifo_state; /* original fifo setting - fifo enabled */
static int fdc_fifo_thr; /* original fifo setting - threshold */
static int fdc_lock_state; /* original lock setting - locked */
static int fdc_fifo_locked; /* has fifo && lock set ? */
static __u8 fdc_precomp; /* default precomp. value (nsec) */
static __u8 fdc_prec_code; /* fdc precomp. select code */
static char ftape_id[] = "ftape"; /* used by request irq and free irq */
static int fdc_set_seek_rate(int seek_rate);
void fdc_catch_stray_interrupts(int count)
{
unsigned long flags;
spin_lock_irqsave(&fdc_io_lock, flags);
if (count == 0) {
ft_expected_stray_interrupts = 0;
} else {
ft_expected_stray_interrupts += count;
}
spin_unlock_irqrestore(&fdc_io_lock, flags);
}
/* Wait during a timeout period for a given FDC status.
* If usecs == 0 then just test status, else wait at least for usecs.
* Returns -ETIME on timeout. Function must be calibrated first !
*/
static int fdc_wait(unsigned int usecs, __u8 mask, __u8 state)
{
int count_1 = (fdc_calibr_count * usecs +
fdc_calibr_count - 1) / fdc_calibr_time;
do {
fdc_status = inb_p(fdc.msr);
if ((fdc_status & mask) == state) {
return 0;
}
} while (count_1-- >= 0);
return -ETIME;
}
int fdc_ready_wait(unsigned int usecs)
{
return fdc_wait(usecs, FDC_DATA_READY | FDC_BUSY, FDC_DATA_READY);
}
/* Why can't we just use udelay()?
*/
static void fdc_usec_wait(unsigned int usecs)
{
fdc_wait(usecs, 0, 1); /* will always timeout ! */
}
static int fdc_ready_out_wait(unsigned int usecs)
{
fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
return fdc_wait(usecs, FDC_DATA_OUT_READY, FDC_DATA_OUT_READY);
}
void fdc_wait_calibrate(void)
{
ftape_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!).
*/
static int fdc_write(const __u8 data)
{
fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
if (fdc_wait(150, FDC_DATA_READY_MASK, FDC_DATA_IN_READY) < 0) {
return -ETIME;
} else {
outb(data, fdc.fifo);
return 0;
}
}
/* Wait for a (short) while for the FDC to become ready
* and transfer the next result byte.
* Return -ETIME if timeout on getting ready (depends on hardware!).
*/
static int fdc_read(__u8 * data)
{
fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
if (fdc_wait(150, FDC_DATA_READY_MASK, FDC_DATA_OUT_READY) < 0) {
return -ETIME;
} else {
*data = inb(fdc.fifo);
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 or ETIME) will occur.
*/
int fdc_command(const __u8 * cmd_data, int cmd_len)
{
int result = 0;
unsigned long flags;
int count = cmd_len;
int retry = 0;
#ifdef TESTING
static unsigned int last_time;
unsigned int time;
#endif
TRACE_FUN(ft_t_any);
fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
spin_lock_irqsave(&fdc_io_lock, flags);
if (!in_interrupt())
/* Yes, I know, too much comments inside this function
* ...
*
* Yet another bug in the original driver. All that
* havoc is caused by the fact that the isr() sends
* itself a command to the floppy tape driver (pause,
* micro step pause). Now, the problem is that
* commands are transmitted via the fdc_seek
* command. But: the fdc performs seeks in the
* background i.e. it doesn't signal busy while
* sending the step pulses to the drive. Therefore the
* non-interrupt level driver has no chance to tell
* whether the isr() just has issued a seek. Therefore
* we HAVE TO have a look at the ft_hide_interrupt
* flag: it signals the non-interrupt level part of
* the driver that it has to wait for the fdc until it
* has completet seeking.
*
* THIS WAS PRESUMABLY THE REASON FOR ALL THAT
* "fdc_read timeout" errors, I HOPE :-)
*/
if (ft_hide_interrupt) {
restore_flags(flags);
TRACE(ft_t_info,
"Waiting for the isr() completing fdc_seek()");
if (fdc_interrupt_wait(2 * FT_SECOND) < 0) {
TRACE(ft_t_warn,
"Warning: timeout waiting for isr() seek to complete");
}
if (ft_hide_interrupt || !ft_seek_completed) {
/* There cannot be another
* interrupt. The isr() only stops
* the tape and the next interrupt
* won't come until we have send our
* command to the drive.
*/
TRACE_ABORT(-EIO, ft_t_bug,
"BUG? isr() is still seeking?\n"
KERN_INFO "hide: %d\n"
KERN_INFO "seek: %d",
ft_hide_interrupt,
ft_seek_completed);
}
fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
spin_lock_irqsave(&fdc_io_lock, flags);
}
fdc_status = inb(fdc.msr);
if ((fdc_status & FDC_DATA_READY_MASK) != FDC_DATA_IN_READY) {
spin_unlock_irqrestore(&fdc_io_lock, flags);
TRACE_ABORT(-EBUSY, ft_t_err, "fdc not ready");
}
fdc_mode = *cmd_data; /* used by isr */
#ifdef TESTING
if (fdc_mode == FDC_SEEK) {
time = ftape_timediff(last_time, ftape_timestamp());
if (time < 6000) {
TRACE(ft_t_bug,"Warning: short timeout between seek commands: %d",
time);
}
}
#endif
if (!in_interrupt()) {
/* shouldn't be cleared if called from isr
*/
ft_interrupt_seen = 0;
}
while (count) {
result = fdc_write(*cmd_data);
if (result < 0) {
TRACE(ft_t_fdc_dma,
"fdc_mode = %02x, status = %02x at index %d",
(int) fdc_mode, (int) fdc_status,
cmd_len - count);
if (++retry <= 3) {
TRACE(ft_t_warn, "fdc_write timeout, retry");
} else {
TRACE(ft_t_err, "fdc_write timeout, fatal");
/* recover ??? */
break;
}
} else {
--count;
++cmd_data;
}
}
#ifdef TESTING
if (fdc_mode == FDC_SEEK) {
last_time = ftape_timestamp();
}
#endif
spin_unlock_irqrestore(&fdc_io_lock, flags);
TRACE_EXIT result;
}
/* Input a res_len long result string from the FDC.
* The FDC should be ready to send the result or an error
* (EBUSY or ETIME) will occur.
*/
int fdc_result(__u8 * res_data, int res_len)
{
int result = 0;
unsigned long flags;
int count = res_len;
int retry = 0;
TRACE_FUN(ft_t_any);
spin_lock_irqsave(&fdc_io_lock, flags);
fdc_status = inb(fdc.msr);
if ((fdc_status & FDC_DATA_READY_MASK) != FDC_DATA_OUT_READY) {
TRACE(ft_t_err, "fdc not ready");
result = -EBUSY;
} else while (count) {
if (!(fdc_status & FDC_BUSY)) {
spin_unlock_irqrestore(&fdc_io_lock, flags);
TRACE_ABORT(-EIO, ft_t_err, "premature end of result phase");
}
result = fdc_read(res_data);
if (result < 0) {
TRACE(ft_t_fdc_dma,
"fdc_mode = %02x, status = %02x at index %d",
(int) fdc_mode,
(int) fdc_status,
res_len - count);
if (++retry <= 3) {
TRACE(ft_t_warn, "fdc_read timeout, retry");
} else {
TRACE(ft_t_err, "fdc_read timeout, fatal");
/* recover ??? */
break;
++retry;
}
} else {
--count;
++res_data;
}
}
spin_unlock_irqrestore(&fdc_io_lock, flags);
fdc_usec_wait(FT_RQM_DELAY); /* allow FDC to negate BSY */
TRACE_EXIT result;
}
/* Handle command and result phases for
* commands without data phase.
*/
static int fdc_issue_command(const __u8 * out_data, int out_count,
__u8 * in_data, int in_count)
{
TRACE_FUN(ft_t_any);
if (out_count > 0) {
TRACE_CATCH(fdc_command(out_data, out_count),);
}
/* will take 24 - 30 usec for fdc_sense_drive_status and
* fdc_sense_interrupt_status commands.
* 35 fails sometimes (5/9/93 SJL)
* On a loaded system it incidentally takes longer than
* this for the fdc to get ready ! ?????? WHY ??????
* So until we know what's going on use a very long timeout.
*/
TRACE_CATCH(fdc_ready_out_wait(500 /* usec */),);
if (in_count > 0) {
TRACE_CATCH(fdc_result(in_data, in_count),
TRACE(ft_t_err, "result phase aborted"));
}
TRACE_EXIT 0;
}
/* Wait for FDC interrupt with timeout (in milliseconds).
* Signals are blocked so the wait will not be aborted.
* Note: interrupts must be enabled ! (23/05/93 SJL)
*/
int fdc_interrupt_wait(unsigned int time)
{
DECLARE_WAITQUEUE(wait,current);
sigset_t old_sigmask;
static int resetting;
long timeout;
TRACE_FUN(ft_t_fdc_dma);
if (waitqueue_active(&ftape_wait_intr)) {
TRACE_ABORT(-EIO, ft_t_err, "error: nested call");
}
/* timeout time will be up to USPT microseconds too long ! */
timeout = (1000 * time + FT_USPT - 1) / FT_USPT;
spin_lock_irq(&current->sighand->siglock);
old_sigmask = current->blocked;
sigfillset(&current->blocked);
recalc_sigpending();
spin_unlock_irq(&current->sighand->siglock);
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&ftape_wait_intr, &wait);
while (!ft_interrupt_seen && timeout)
timeout = schedule_timeout_interruptible(timeout);
spin_lock_irq(&current->sighand->siglock);
current->blocked = old_sigmask;
recalc_sigpending();
spin_unlock_irq(&current->sighand->siglock);
remove_wait_queue(&ftape_wait_intr, &wait);
/* the following IS necessary. True: as well
* wake_up_interruptible() as the schedule() set TASK_RUNNING
* when they wakeup a task, BUT: it may very well be that
* ft_interrupt_seen is already set to 1 when we enter here
* in which case schedule() gets never called, and
* TASK_RUNNING never set. This has the funny effect that we
* execute all the code until we leave kernel space, but then
* the task is stopped (a task CANNOT be preempted while in
* kernel mode. Sending a pair of SIGSTOP/SIGCONT to the
* tasks wakes it up again. Funny! :-)
*/
current->state = TASK_RUNNING;
if (ft_interrupt_seen) { /* woken up by interrupt */
ft_interrupt_seen = 0;
TRACE_EXIT 0;
}
/* Original comment:
* In first instance, next statement seems unnecessary since
* it will be cleared in fdc_command. However, a small part of
* the software seems to rely on this being cleared here
* (ftape_close might fail) so stick to it until things get fixed !
*/
/* My deeply sought of knowledge:
* Behold NO! It is obvious. fdc_reset() doesn't call fdc_command()
* but nevertheless uses fdc_interrupt_wait(). OF COURSE this needs to
* be reset here.
*/
ft_interrupt_seen = 0; /* clear for next call */
if (!resetting) {
resetting = 1; /* break infinite recursion if reset fails */
TRACE(ft_t_any, "cleanup reset");
fdc_reset();
resetting = 0;
}
TRACE_EXIT (signal_pending(current)) ? -EINTR : -ETIME;
}
/* Start/stop drive motor. Enable DMA mode.
*/
void fdc_motor(int motor)
{
int unit = ft_drive_sel;
int data = unit | FDC_RESET_NOT | FDC_DMA_MODE;
TRACE_FUN(ft_t_any);
ftape_motor = motor;
if (ftape_motor) {
data |= FDC_MOTOR_0 << unit;
TRACE(ft_t_noise, "turning motor %d on", unit);
} else {
TRACE(ft_t_noise, "turning motor %d off", unit);
}
if (ft_mach2) {
outb_p(data, fdc.dor2);
} else {
outb_p(data, fdc.dor);
}
ftape_sleep(10 * FT_MILLISECOND);
TRACE_EXIT;
}
static void fdc_update_dsr(void)
{
TRACE_FUN(ft_t_any);
TRACE(ft_t_flow, "rate = %d Kbps, precomp = %d ns",
fdc_data_rate, fdc_precomp);
if (fdc.type >= i82077) {
outb_p((fdc_rate_code & 0x03) | fdc_prec_code, fdc.dsr);
} else {
outb_p(fdc_rate_code & 0x03, fdc.ccr);
}
TRACE_EXIT;
}
void fdc_set_write_precomp(int precomp)
{
TRACE_FUN(ft_t_any);
TRACE(ft_t_noise, "New precomp: %d nsec", precomp);
fdc_precomp = precomp;
/* write precompensation can be set in multiples of 41.67 nsec.
* round the parameter to the nearest multiple and convert it
* into a fdc setting. Note that 0 means default to the fdc,
* 7 is used instead of that.
*/
fdc_prec_code = ((fdc_precomp + 21) / 42) << 2;
if (fdc_prec_code == 0 || fdc_prec_code > (6 << 2)) {
fdc_prec_code = 7 << 2;
}
fdc_update_dsr();
TRACE_EXIT;
}
/* Reprogram the 82078 registers to use Data Rate Table 1 on all drives.
*/
static void fdc_set_drive_specs(void)
{
__u8 cmd[] = { FDC_DRIVE_SPEC, 0x00, 0x00, 0x00, 0x00, 0xc0};
int result;
TRACE_FUN(ft_t_any);
TRACE(ft_t_flow, "Setting of drive specs called");
if (fdc.type >= i82078_1) {
cmd[1] = (0 << 5) | (2 << 2);
cmd[2] = (1 << 5) | (2 << 2);
cmd[3] = (2 << 5) | (2 << 2);
cmd[4] = (3 << 5) | (2 << 2);
result = fdc_command(cmd, NR_ITEMS(cmd));
if (result < 0) {
TRACE(ft_t_err, "Setting of drive specs failed");
}
}
TRACE_EXIT;
}
/* Select clock for fdc, must correspond with tape drive setting !
* This also influences the fdc timing so we must adjust some values.
*/
int fdc_set_data_rate(int rate)
{
int bad_rate = 0;
TRACE_FUN(ft_t_any);
/* Select clock for fdc, must correspond with tape drive setting !
* This also influences the fdc timing so we must adjust some values.
*/
TRACE(ft_t_fdc_dma, "new rate = %d", rate);
switch (rate) {
case 250:
fdc_rate_code = fdc_data_rate_250;
break;
case 500:
fdc_rate_code = fdc_data_rate_500;
break;
case 1000:
if (fdc.type < i82077) {
bad_rate = 1;
} else {
fdc_rate_code = fdc_data_rate_1000;
}
break;
case 2000:
if (fdc.type < i82078_1) {
bad_rate = 1;
} else {
fdc_rate_code = fdc_data_rate_2000;
}
break;
default:
bad_rate = 1;
}
if (bad_rate) {
TRACE_ABORT(-EIO,
ft_t_fdc_dma, "%d is not a valid data rate", rate);
}
fdc_data_rate = rate;
fdc_update_dsr();
fdc_set_seek_rate(fdc_seek_rate); /* clock changed! */
ftape_udelay(1000);
TRACE_EXIT 0;
}
/* keep the unit select if keep_select is != 0,
*/
static void fdc_dor_reset(int keep_select)
{
__u8 fdc_ctl = ft_drive_sel;
if (keep_select != 0) {
fdc_ctl |= FDC_DMA_MODE;
if (ftape_motor) {
fdc_ctl |= FDC_MOTOR_0 << ft_drive_sel;
}
}
ftape_udelay(10); /* ??? but seems to be necessary */
if (ft_mach2) {
outb_p(fdc_ctl & 0x0f, fdc.dor);
outb_p(fdc_ctl, fdc.dor2);
} else {
outb_p(fdc_ctl, fdc.dor);
}
fdc_usec_wait(10); /* delay >= 14 fdc clocks */
if (keep_select == 0) {
fdc_ctl = 0;
}
fdc_ctl |= FDC_RESET_NOT;
if (ft_mach2) {
outb_p(fdc_ctl & 0x0f, fdc.dor);
outb_p(fdc_ctl, fdc.dor2);
} else {
outb_p(fdc_ctl, fdc.dor);
}
}
/* Reset the floppy disk controller. Leave the ftape_unit selected.
*/
void fdc_reset(void)
{
int st0;
int i;
int dummy;
unsigned long flags;
TRACE_FUN(ft_t_any);
spin_lock_irqsave(&fdc_io_lock, flags);
fdc_dor_reset(1); /* keep unit selected */
fdc_mode = fdc_idle;
/* maybe the spin_lock_irq* pair is not necessary, BUT:
* the following line MUST be here. Otherwise fdc_interrupt_wait()
* won't wait. Note that fdc_reset() is called from
* ftape_dumb_stop() when the fdc is busy transferring data. In this
* case fdc_isr() MOST PROBABLY sets ft_interrupt_seen, and tries
* to get the result bytes from the fdc etc. CLASH.
*/
ft_interrupt_seen = 0;
/* Program data rate
*/
fdc_update_dsr(); /* restore data rate and precomp */
spin_unlock_irqrestore(&fdc_io_lock, flags);
/*
* Wait for first polling cycle to complete
*/
if (fdc_interrupt_wait(1 * FT_SECOND) < 0) {
TRACE(ft_t_err, "no drive polling interrupt!");
} else { /* clear all disk-changed statuses */
for (i = 0; i < 4; ++i) {
if(fdc_sense_interrupt_status(&st0, &dummy) != 0) {
TRACE(ft_t_err, "sense failed for %d", i);
}
if (i == ft_drive_sel) {
ftape_current_cylinder = dummy;
}
}
TRACE(ft_t_noise, "drive polling completed");
}
/*
* SPECIFY COMMAND
*/
fdc_set_seek_rate(fdc_seek_rate);
/*
* DRIVE SPECIFICATION COMMAND (if fdc type known)
*/
if (fdc.type >= i82078_1) {
fdc_set_drive_specs();
}
TRACE_EXIT;
}
#if !defined(CLK_48MHZ)
# define CLK_48MHZ 1
#endif
/* 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)
{
__u8 cmd1[] = {FDC_CONFIGURE, 0x00, 0x00, 0x00};
__u8 cmd2[] = {FDC_LOCK};
__u8 cmd3[] = {FDC_UNLOCK};
__u8 stat[1];
TRACE_FUN(ft_t_flow);
if (!fdc_fifo_locked) {
fdc_reset();
TRACE_EXIT;
}
if (fdc_issue_command(cmd3, 1, stat, 1) < 0 || stat[0] != 0x00) {
fdc_dor_reset(0);
TRACE_ABORT(/**/, ft_t_bug,
"couldn't unlock fifo, configuration remains changed");
}
fdc_fifo_locked = 0;
if (CLK_48MHZ && fdc.type >= i82078) {
cmd1[0] |= FDC_CLK48_BIT;
}
cmd1[2] = ((fdc_fifo_state) ? 0 : 0x20) + (fdc_fifo_thr - 1);
if (fdc_command(cmd1, NR_ITEMS(cmd1)) < 0) {
fdc_dor_reset(0);
TRACE_ABORT(/**/, ft_t_bug,
"couldn't reconfigure fifo to old state");
}
if (fdc_lock_state &&
fdc_issue_command(cmd2, 1, stat, 1) < 0) {
fdc_dor_reset(0);
TRACE_ABORT(/**/, ft_t_bug, "couldn't lock old state again");
}
TRACE(ft_t_noise, "fifo restored: %sabled, thr. %d, %slocked",
fdc_fifo_state ? "en" : "dis",
fdc_fifo_thr, (fdc_lock_state) ? "" : "not ");
fdc_dor_reset(0);
TRACE_EXIT;
}
/* Specify FDC seek-rate (milliseconds)
*/
static int fdc_set_seek_rate(int seek_rate)
{
/* set step rate, dma mode, and minimal head load and unload times
*/
__u8 in[3] = { FDC_SPECIFY, 1, (1 << 1)};
fdc_seek_rate = seek_rate;
in[1] |= (16 - (fdc_data_rate * fdc_seek_rate) / 500) << 4;
return fdc_command(in, 3);
}
/* Sense drive status: get unit's drive status (ST3)
*/
int fdc_sense_drive_status(int *st3)
{
__u8 out[2];
__u8 in[1];
TRACE_FUN(ft_t_any);
out[0] = FDC_SENSED;
out[1] = ft_drive_sel;
TRACE_CATCH(fdc_issue_command(out, 2, in, 1),);
*st3 = in[0];
TRACE_EXIT 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)
{
__u8 out[1];
__u8 in[2];
TRACE_FUN(ft_t_any);
out[0] = FDC_SENSEI;
TRACE_CATCH(fdc_issue_command(out, 1, in, 2),);
*st0 = in[0];
*current_cylinder = in[1];
TRACE_EXIT 0;
}
/* step to track
*/
int fdc_seek(int track)
{
__u8 out[3];
int st0, pcn;
#ifdef TESTING
unsigned int time;
#endif
TRACE_FUN(ft_t_any);
out[0] = FDC_SEEK;
out[1] = ft_drive_sel;
out[2] = track;
#ifdef TESTING
time = ftape_timestamp();
#endif
/* We really need this command to work !
*/
ft_seek_completed = 0;
TRACE_CATCH(fdc_command(out, 3),
fdc_reset();
TRACE(ft_t_noise, "destination was: %d, resetting FDC...",
track));
/* Handle interrupts until ft_seek_completed or timeout.
*/
for (;;) {
TRACE_CATCH(fdc_interrupt_wait(2 * FT_SECOND),);
if (ft_seek_completed) {
TRACE_CATCH(fdc_sense_interrupt_status(&st0, &pcn),);
if ((st0 & ST0_SEEK_END) == 0) {
TRACE_ABORT(-EIO, ft_t_err,
"no seek-end after seek completion !??");
}
break;
}
}
#ifdef TESTING
time = ftape_timediff(time, ftape_timestamp()) / abs(track - ftape_current_cylinder);
if ((time < 900 || time > 3100) && abs(track - ftape_current_cylinder) > 5) {
TRACE(ft_t_warn, "Wrong FDC STEP interval: %d usecs (%d)",
time, track - ftape_current_cylinder);
}
#endif
/* Verify whether we issued the right tape command.
*/
/* Verify that we seek to the proper track. */
if (pcn != track) {
TRACE_ABORT(-EIO, ft_t_err, "bad seek..");
}
ftape_current_cylinder = track;
TRACE_EXIT 0;
}
static int perpend_mode; /* set if fdc is in perpendicular mode */
static int perpend_off(void)
{
__u8 perpend[] = {FDC_PERPEND, 0x00};
TRACE_FUN(ft_t_any);
if (perpend_mode) {
/* Turn off perpendicular mode */
perpend[1] = 0x80;
TRACE_CATCH(fdc_command(perpend, 2),
TRACE(ft_t_err,"Perpendicular mode exit failed!"));
perpend_mode = 0;
}
TRACE_EXIT 0;
}
static int handle_perpend(int segment_id)
{
__u8 perpend[] = {FDC_PERPEND, 0x00};
TRACE_FUN(ft_t_any);
/* When writing QIC-3020 tapes, turn on perpendicular mode
* if tape is moving in forward direction (even tracks).
*/
if (ft_qic_std == QIC_TAPE_QIC3020 &&
((segment_id / ft_segments_per_track) & 1) == 0) {
/* FIXME: some i82077 seem to support perpendicular mode as
* well.
*/
#if 0
if (fdc.type < i82077AA) {}
#else
if (fdc.type < i82077 && ft_data_rate < 1000) {
#endif
/* fdc does not support perpendicular mode: complain
*/
TRACE_ABORT(-EIO, ft_t_err,
"Your FDC does not support QIC-3020.");
}
perpend[1] = 0x03 /* 0x83 + (0x4 << ft_drive_sel) */ ;
TRACE_CATCH(fdc_command(perpend, 2),
TRACE(ft_t_err,"Perpendicular mode entry failed!"));
TRACE(ft_t_flow, "Perpendicular mode set");
perpend_mode = 1;
TRACE_EXIT 0;
}
TRACE_EXIT perpend_off();
}
static inline void fdc_setup_dma(char mode,
volatile void *addr, unsigned int count)
{
/* Program the DMA controller.
*/
disable_dma(fdc.dma);
clear_dma_ff(fdc.dma);
set_dma_mode(fdc.dma, mode);
set_dma_addr(fdc.dma, virt_to_bus((void*)addr));
set_dma_count(fdc.dma, count);
enable_dma(fdc.dma);
}
/* Setup fdc and dma for formatting the next segment
*/
int fdc_setup_formatting(buffer_struct * buff)
{
unsigned long flags;
__u8 out[6] = {
FDC_FORMAT, 0x00, 3, 4 * FT_SECTORS_PER_SEGMENT, 0x00, 0x6b
};
TRACE_FUN(ft_t_any);
TRACE_CATCH(handle_perpend(buff->segment_id),);
/* Program the DMA controller.
*/
TRACE(ft_t_fdc_dma,
"phys. addr. = %lx", virt_to_bus((void*) buff->ptr));
spin_lock_irqsave(&fdc_io_lock, flags);
fdc_setup_dma(DMA_MODE_WRITE, buff->ptr, FT_SECTORS_PER_SEGMENT * 4);
/* Issue FDC command to start reading/writing.
*/
out[1] = ft_drive_sel;
out[4] = buff->gap3;
TRACE_CATCH(fdc_setup_error = fdc_command(out, sizeof(out)),
restore_flags(flags); fdc_mode = fdc_idle);
spin_unlock_irqrestore(&fdc_io_lock, flags);
TRACE_EXIT 0;
}
/* Setup Floppy Disk Controller and DMA to read or write the next cluster
* of good sectors from or to the current segment.
*/
int fdc_setup_read_write(buffer_struct * buff, __u8 operation)
{
unsigned long flags;
__u8 out[9];
int dma_mode;
TRACE_FUN(ft_t_any);
switch(operation) {
case FDC_VERIFY:
if (fdc.type < i82077) {
operation = FDC_READ;
}
case FDC_READ:
case FDC_READ_DELETED:
dma_mode = DMA_MODE_READ;
TRACE(ft_t_fdc_dma, "xfer %d sectors to 0x%p",
buff->sector_count, buff->ptr);
TRACE_CATCH(perpend_off(),);
break;
case FDC_WRITE_DELETED:
TRACE(ft_t_noise, "deleting segment %d", buff->segment_id);
case FDC_WRITE:
dma_mode = DMA_MODE_WRITE;
/* When writing QIC-3020 tapes, turn on perpendicular mode
* if tape is moving in forward direction (even tracks).
*/
TRACE_CATCH(handle_perpend(buff->segment_id),);
TRACE(ft_t_fdc_dma, "xfer %d sectors from 0x%p",
buff->sector_count, buff->ptr);
break;
default:
TRACE_ABORT(-EIO,
ft_t_bug, "bug: invalid operation parameter");
}
TRACE(ft_t_fdc_dma, "phys. addr. = %lx",virt_to_bus((void*)buff->ptr));
spin_lock_irqsave(&fdc_io_lock, flags);
if (operation != FDC_VERIFY) {
fdc_setup_dma(dma_mode, buff->ptr,
FT_SECTOR_SIZE * buff->sector_count);
}
/* Issue FDC command to start reading/writing.
*/
out[0] = operation;
out[1] = ft_drive_sel;
out[2] = buff->cyl;
out[3] = buff->head;
out[4] = buff->sect + buff->sector_offset;
out[5] = 3; /* Sector size of 1K. */
out[6] = out[4] + buff->sector_count - 1; /* last sector */
out[7] = 109; /* Gap length. */
out[8] = 0xff; /* No limit to transfer size. */
TRACE(ft_t_fdc_dma, "C: 0x%02x, H: 0x%02x, R: 0x%02x, cnt: 0x%02x",
out[2], out[3], out[4], out[6] - out[4] + 1);
spin_unlock_irqrestore(&fdc_io_lock, flags);
TRACE_CATCH(fdc_setup_error = fdc_command(out, 9),fdc_mode = fdc_idle);
TRACE_EXIT 0;
}
int fdc_fifo_threshold(__u8 threshold,
int *fifo_state, int *lock_state, int *fifo_thr)
{
const __u8 cmd0[] = {FDC_DUMPREGS};
__u8 cmd1[] = {FDC_CONFIGURE, 0, (0x0f & (threshold - 1)), 0};
const __u8 cmd2[] = {FDC_LOCK};
const __u8 cmd3[] = {FDC_UNLOCK};
__u8 reg[10];
__u8 stat;
int i;
int result;
TRACE_FUN(ft_t_any);
if (CLK_48MHZ && fdc.type >= i82078) {
cmd1[0] |= FDC_CLK48_BIT;
}
/* Dump fdc internal registers for examination
*/
TRACE_CATCH(fdc_command(cmd0, NR_ITEMS(cmd0)),
TRACE(ft_t_warn, "dumpreg cmd failed, fifo unchanged"));
/* Now read fdc internal registers from fifo
*/
for (i = 0; i < (int)NR_ITEMS(reg); ++i) {
fdc_read(&reg[i]);
TRACE(ft_t_fdc_dma, "Register %d = 0x%02x", i, reg[i]);
}
if (fifo_state && lock_state && fifo_thr) {
*fifo_state = (reg[8] & 0x20) == 0;
*lock_state = reg[7] & 0x80;
*fifo_thr = 1 + (reg[8] & 0x0f);
}
TRACE(ft_t_noise,
"original fifo state: %sabled, threshold %d, %slocked",
((reg[8] & 0x20) == 0) ? "en" : "dis",
1 + (reg[8] & 0x0f), (reg[7] & 0x80) ? "" : "not ");
/* If fdc is already locked, unlock it first ! */
if (reg[7] & 0x80) {
fdc_ready_wait(100);
TRACE_CATCH(fdc_issue_command(cmd3, NR_ITEMS(cmd3), &stat, 1),
TRACE(ft_t_bug, "FDC unlock command failed, "
"configuration unchanged"));
}
fdc_fifo_locked = 0;
/* Enable fifo and set threshold at xx bytes to allow a
* reasonably large latency and reduce number of dma bursts.
*/
fdc_ready_wait(100);
if ((result = fdc_command(cmd1, NR_ITEMS(cmd1))) < 0) {
TRACE(ft_t_bug, "configure cmd failed, fifo unchanged");
}
/* Now lock configuration so reset will not change it
*/
if(fdc_issue_command(cmd2, NR_ITEMS(cmd2), &stat, 1) < 0 ||
stat != 0x10) {
TRACE_ABORT(-EIO, ft_t_bug,
"FDC lock command failed, stat = 0x%02x", stat);
}
fdc_fifo_locked = 1;
TRACE_EXIT result;
}
static int fdc_fifo_enable(void)
{
TRACE_FUN(ft_t_any);
if (fdc_fifo_locked) {
TRACE_ABORT(0, ft_t_warn, "Fifo not enabled because locked");
}
TRACE_CATCH(fdc_fifo_threshold(ft_fdc_threshold /* bytes */,
&fdc_fifo_state,
&fdc_lock_state,
&fdc_fifo_thr),);
TRACE_CATCH(fdc_fifo_threshold(ft_fdc_threshold /* bytes */,
NULL, NULL, NULL),);
TRACE_EXIT 0;
}
/* Determine fd controller type
*/
static __u8 fdc_save_state[2];
static int fdc_probe(void)
{
__u8 cmd[1];
__u8 stat[16]; /* must be able to hold dumpregs & save results */
int i;
TRACE_FUN(ft_t_any);
/* Try to find out what kind of fd controller we have to deal with
* Scheme borrowed from floppy driver:
* first try if FDC_DUMPREGS command works
* (this indicates that we have a 82072 or better)
* then try the FDC_VERSION command (82072 doesn't support this)
* then try the FDC_UNLOCK command (some older 82077's don't support this)
* then try the FDC_PARTID command (82078's support this)
*/
cmd[0] = FDC_DUMPREGS;
if (fdc_issue_command(cmd, 1, stat, 1) != 0) {
TRACE_ABORT(no_fdc, ft_t_bug, "No FDC found");
}
if (stat[0] == 0x80) {
/* invalid command: must be pre 82072 */
TRACE_ABORT(i8272,
ft_t_warn, "Type 8272A/765A compatible FDC found");
}
fdc_result(&stat[1], 9);
fdc_save_state[0] = stat[7];
fdc_save_state[1] = stat[8];
cmd[0] = FDC_VERSION;
if (fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] == 0x80) {
TRACE_ABORT(i8272, ft_t_warn, "Type 82072 FDC found");
}
if (*stat != 0x90) {
TRACE_ABORT(i8272, ft_t_warn, "Unknown FDC found");
}
cmd[0] = FDC_UNLOCK;
if(fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] != 0x00) {
TRACE_ABORT(i8272, ft_t_warn,
"Type pre-1991 82077 FDC found, "
"treating it like a 82072");
}
if (fdc_save_state[0] & 0x80) { /* was locked */
cmd[0] = FDC_LOCK; /* restore lock */
(void)fdc_issue_command(cmd, 1, stat, 1);
TRACE(ft_t_warn, "FDC is already locked");
}
/* Test for a i82078 FDC */
cmd[0] = FDC_PARTID;
if (fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] == 0x80) {
/* invalid command: not a i82078xx type FDC */
for (i = 0; i < 4; ++i) {
outb_p(i, fdc.tdr);
if ((inb_p(fdc.tdr) & 0x03) != i) {
TRACE_ABORT(i82077,
ft_t_warn, "Type 82077 FDC found");
}
}
TRACE_ABORT(i82077AA, ft_t_warn, "Type 82077AA FDC found");
}
/* FDC_PARTID cmd succeeded */
switch (stat[0] >> 5) {
case 0x0:
/* i82078SL or i82078-1. The SL part cannot run at
* 2Mbps (the SL and -1 dies are identical; they are
* speed graded after production, according to Intel).
* Some SL's can be detected by doing a SAVE cmd and
* look at bit 7 of the first byte (the SEL3V# bit).
* If it is 0, the part runs off 3Volts, and hence it
* is a SL.
*/
cmd[0] = FDC_SAVE;
if(fdc_issue_command(cmd, 1, stat, 16) < 0) {
TRACE(ft_t_err, "FDC_SAVE failed. Dunno why");
/* guess we better claim the fdc to be a i82078 */
TRACE_ABORT(i82078,
ft_t_warn,
"Type i82078 FDC (i suppose) found");
}
if ((stat[0] & FDC_SEL3V_BIT)) {
/* fdc running off 5Volts; Pray that it's a i82078-1
*/
TRACE_ABORT(i82078_1, ft_t_warn,
"Type i82078-1 or 5Volt i82078SL FDC found");
}
TRACE_ABORT(i82078, ft_t_warn,
"Type 3Volt i82078SL FDC (1Mbps) found");
case 0x1:
case 0x2: /* S82078B */
/* The '78B isn't '78 compatible. Detect it as a '77AA */
TRACE_ABORT(i82077AA, ft_t_warn, "Type i82077AA FDC found");
case 0x3: /* NSC PC8744 core; used in several super-IO chips */
TRACE_ABORT(i82077AA,
ft_t_warn, "Type 82077AA compatible FDC found");
default:
TRACE(ft_t_warn, "A previously undetected FDC found");
TRACE_ABORT(i82077AA, ft_t_warn,
"Treating it as a 82077AA. Please report partid= %d",
stat[0]);
} /* switch(stat[ 0] >> 5) */
TRACE_EXIT no_fdc;
}
static int fdc_request_regions(void)
{
TRACE_FUN(ft_t_flow);
if (ft_mach2 || ft_probe_fc10) {
if (!request_region(fdc.sra, 8, "fdc (ft)")) {
#ifndef BROKEN_FLOPPY_DRIVER
TRACE_EXIT -EBUSY;
#else
TRACE(ft_t_warn,
"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra);
#endif
}
} else {
if (!request_region(fdc.sra, 6, "fdc (ft)")) {
#ifndef BROKEN_FLOPPY_DRIVER
TRACE_EXIT -EBUSY;
#else
TRACE(ft_t_warn,
"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra);
#endif
}
if (!request_region(fdc.sra + 7, 1, "fdc (ft)")) {
#ifndef BROKEN_FLOPPY_DRIVER
release_region(fdc.sra, 6);
TRACE_EXIT -EBUSY;
#else
TRACE(ft_t_warn,
"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra + 7);
#endif
}
}
TRACE_EXIT 0;
}
void fdc_release_regions(void)
{
TRACE_FUN(ft_t_flow);
if (fdc.sra != 0) {
if (fdc.dor2 != 0) {
release_region(fdc.sra, 8);
} else {
release_region(fdc.sra, 6);
release_region(fdc.dir, 1);
}
}
TRACE_EXIT;
}
static int fdc_config_regs(unsigned int fdc_base,
unsigned int fdc_irq,
unsigned int fdc_dma)
{
TRACE_FUN(ft_t_flow);
fdc.irq = fdc_irq;
fdc.dma = fdc_dma;
fdc.sra = fdc_base;
fdc.srb = fdc_base + 1;
fdc.dor = fdc_base + 2;
fdc.tdr = fdc_base + 3;
fdc.msr = fdc.dsr = fdc_base + 4;
fdc.fifo = fdc_base + 5;
fdc.dir = fdc.ccr = fdc_base + 7;
fdc.dor2 = (ft_mach2 || ft_probe_fc10) ? fdc_base + 6 : 0;
TRACE_CATCH(fdc_request_regions(), fdc.sra = 0);
TRACE_EXIT 0;
}
static int fdc_config(void)
{
static int already_done;
TRACE_FUN(ft_t_any);
if (already_done) {
TRACE_CATCH(fdc_request_regions(),);
*(fdc.hook) = fdc_isr; /* hook our handler in */
TRACE_EXIT 0;
}
if (ft_probe_fc10) {
int fc_type;
TRACE_CATCH(fdc_config_regs(ft_fdc_base,
ft_fdc_irq, ft_fdc_dma),);
fc_type = fc10_enable();
if (fc_type != 0) {
TRACE(ft_t_warn, "FC-%c0 controller found", '0' + fc_type);
fdc.type = fc10;
fdc.hook = &do_ftape;
*(fdc.hook) = fdc_isr; /* hook our handler in */
already_done = 1;
TRACE_EXIT 0;
} else {
TRACE(ft_t_warn, "FC-10/20 controller not found");
fdc_release_regions();
fdc.type = no_fdc;
ft_probe_fc10 = 0;
ft_fdc_base = 0x3f0;
ft_fdc_irq = 6;
ft_fdc_dma = 2;
}
}
TRACE(ft_t_warn, "fdc base: 0x%x, irq: %d, dma: %d",
ft_fdc_base, ft_fdc_irq, ft_fdc_dma);
TRACE_CATCH(fdc_config_regs(ft_fdc_base, ft_fdc_irq, ft_fdc_dma),);
fdc.hook = &do_ftape;
*(fdc.hook) = fdc_isr; /* hook our handler in */
already_done = 1;
TRACE_EXIT 0;
}
static irqreturn_t ftape_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
void (*handler) (void) = *fdc.hook;
int handled = 0;
TRACE_FUN(ft_t_any);
*fdc.hook = NULL;
if (handler) {
handled = 1;
handler();
} else {
TRACE(ft_t_bug, "Unexpected ftape interrupt");
}
TRACE_EXIT IRQ_RETVAL(handled);
}
static int fdc_grab_irq_and_dma(void)
{
TRACE_FUN(ft_t_any);
if (fdc.hook == &do_ftape) {
/* Get fast interrupt handler.
*/
if (request_irq(fdc.irq, ftape_interrupt,
IRQF_DISABLED, "ft", ftape_id)) {
TRACE_ABORT(-EIO, ft_t_bug,
"Unable to grab IRQ%d for ftape driver",
fdc.irq);
}
if (request_dma(fdc.dma, ftape_id)) {
free_irq(fdc.irq, ftape_id);
TRACE_ABORT(-EIO, ft_t_bug,
"Unable to grab DMA%d for ftape driver",
fdc.dma);
}
}
if (ft_fdc_base != 0x3f0 && (ft_fdc_dma == 2 || ft_fdc_irq == 6)) {
/* Using same dma channel or irq as standard fdc, need
* to disable the dma-gate on the std fdc. This
* couldn't be done in the floppy driver as some
* laptops are using the dma-gate to enter a low power
* or even suspended state :-(
*/
outb_p(FDC_RESET_NOT, 0x3f2);
TRACE(ft_t_noise, "DMA-gate on standard fdc disabled");
}
TRACE_EXIT 0;
}
int fdc_release_irq_and_dma(void)
{
TRACE_FUN(ft_t_any);
if (fdc.hook == &do_ftape) {
disable_dma(fdc.dma); /* just in case... */
free_dma(fdc.dma);
free_irq(fdc.irq, ftape_id);
}
if (ft_fdc_base != 0x3f0 && (ft_fdc_dma == 2 || ft_fdc_irq == 6)) {
/* Using same dma channel as standard fdc, need to
* disable the dma-gate on the std fdc. This couldn't
* be done in the floppy driver as some laptops are
* using the dma-gate to enter a low power or even
* suspended state :-(
*/
outb_p(FDC_RESET_NOT | FDC_DMA_MODE, 0x3f2);
TRACE(ft_t_noise, "DMA-gate on standard fdc enabled again");
}
TRACE_EXIT 0;
}
int fdc_init(void)
{
TRACE_FUN(ft_t_any);
/* find a FDC to use */
TRACE_CATCH(fdc_config(),);
TRACE_CATCH(fdc_grab_irq_and_dma(), fdc_release_regions());
ftape_motor = 0;
fdc_catch_stray_interrupts(0); /* clear number of awainted
* stray interrupte
*/
fdc_catch_stray_interrupts(1); /* one always comes (?) */
TRACE(ft_t_flow, "resetting fdc");
fdc_set_seek_rate(2); /* use nominal QIC step rate */
fdc_reset(); /* init fdc & clear track counters */
if (fdc.type == no_fdc) { /* no FC-10 or FC-20 found */
fdc.type = fdc_probe();
fdc_reset(); /* update with new knowledge */
}
if (fdc.type == no_fdc) {
fdc_release_irq_and_dma();
fdc_release_regions();
TRACE_EXIT -ENXIO;
}
if (fdc.type >= i82077) {
if (fdc_fifo_enable() < 0) {
TRACE(ft_t_warn, "couldn't enable fdc fifo !");
} else {
TRACE(ft_t_flow, "fdc fifo enabled and locked");
}
}
TRACE_EXIT 0;
}