blob: 068b426b692f8c27e608e1b075f762553f1d32be [file] [log] [blame]
/*
* SCSI low-level driver for the 53c94 SCSI bus adaptor found
* on Power Macintosh computers, controlling the external SCSI chain.
* We assume the 53c94 is connected to a DBDMA (descriptor-based DMA)
* controller.
*
* Paul Mackerras, August 1996.
* Copyright (C) 1996 Paul Mackerras.
*/
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/blk.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/spinlock.h>
#include <asm/dbdma.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/prom.h>
#include <asm/system.h>
#include "scsi.h"
#include "hosts.h"
#include "mac53c94.h"
enum fsc_phase {
idle,
selecting,
dataing,
completing,
busfreeing,
};
struct fsc_state {
volatile struct mac53c94_regs *regs;
int intr;
volatile struct dbdma_regs *dma;
int dmaintr;
int clk_freq;
struct Scsi_Host *host;
struct fsc_state *next;
Scsi_Cmnd *request_q;
Scsi_Cmnd *request_qtail;
Scsi_Cmnd *current_req; /* req we're currently working on */
enum fsc_phase phase; /* what we're currently trying to do */
struct dbdma_cmd *dma_cmds; /* space for dbdma commands, aligned */
void *dma_cmd_space;
};
static struct fsc_state *all_53c94s;
static void mac53c94_init(struct fsc_state *);
static void mac53c94_start(struct fsc_state *);
static void mac53c94_interrupt(int, void *, struct pt_regs *);
static void do_mac53c94_interrupt(int, void *, struct pt_regs *);
static void cmd_done(struct fsc_state *, int result);
static void set_dma_cmds(struct fsc_state *, Scsi_Cmnd *);
static int data_goes_out(Scsi_Cmnd *);
int
mac53c94_detect(Scsi_Host_Template *tp)
{
struct device_node *node;
int nfscs;
struct fsc_state *state, **prev_statep;
struct Scsi_Host *host;
void *dma_cmd_space;
unsigned char *clkprop;
int proplen;
nfscs = 0;
prev_statep = &all_53c94s;
for (node = find_devices("53c94"); node != 0; node = node->next) {
if (node->n_addrs != 2 || node->n_intrs != 2)
panic("53c94: expected 2 addrs and intrs (got %d/%d)",
node->n_addrs, node->n_intrs);
host = scsi_register(tp, sizeof(struct fsc_state));
if (host == NULL)
break;
host->unique_id = nfscs;
#ifndef MODULE
note_scsi_host(node, host);
#endif
state = (struct fsc_state *) host->hostdata;
if (state == 0)
panic("no 53c94 state");
state->host = host;
state->regs = (volatile struct mac53c94_regs *)
ioremap(node->addrs[0].address, 0x1000);
state->intr = node->intrs[0].line;
state->dma = (volatile struct dbdma_regs *)
ioremap(node->addrs[1].address, 0x1000);
state->dmaintr = node->intrs[1].line;
clkprop = get_property(node, "clock-frequency", &proplen);
if (clkprop == NULL || proplen != sizeof(int)) {
printk(KERN_ERR "%s: can't get clock frequency\n",
node->full_name);
state->clk_freq = 25000000;
} else
state->clk_freq = *(int *)clkprop;
/* Space for dma command list: +1 for stop command,
+1 to allow for aligning. */
dma_cmd_space = kmalloc((host->sg_tablesize + 2) *
sizeof(struct dbdma_cmd), GFP_KERNEL);
if (dma_cmd_space == 0)
panic("53c94: couldn't allocate dma command space");
state->dma_cmds = (struct dbdma_cmd *)
DBDMA_ALIGN(dma_cmd_space);
memset(state->dma_cmds, 0, (host->sg_tablesize + 1)
* sizeof(struct dbdma_cmd));
state->dma_cmd_space = dma_cmd_space;
*prev_statep = state;
prev_statep = &state->next;
if (request_irq(state->intr, do_mac53c94_interrupt, 0,
"53C94", state)) {
printk(KERN_ERR "mac53C94: can't get irq %d\n", state->intr);
}
mac53c94_init(state);
++nfscs;
}
return nfscs;
}
int
mac53c94_release(struct Scsi_Host *host)
{
struct fsc_state *fp = (struct fsc_state *) host->hostdata;
if (fp == 0)
return 0;
if (fp->regs)
iounmap((void *) fp->regs);
if (fp->dma)
iounmap((void *) fp->dma);
kfree(fp->dma_cmd_space);
free_irq(fp->intr, fp);
return 0;
}
int
mac53c94_queue(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *))
{
unsigned long flags;
struct fsc_state *state;
#if 0
if (data_goes_out(cmd)) {
int i;
printk(KERN_DEBUG "mac53c94_queue %p: command is", cmd);
for (i = 0; i < cmd->cmd_len; ++i)
printk(" %.2x", cmd->cmnd[i]);
printk("\n" KERN_DEBUG "use_sg=%d request_bufflen=%d request_buffer=%p\n",
cmd->use_sg, cmd->request_bufflen, cmd->request_buffer);
}
#endif
cmd->scsi_done = done;
cmd->host_scribble = NULL;
state = (struct fsc_state *) cmd->host->hostdata;
save_flags(flags);
cli();
if (state->request_q == NULL)
state->request_q = cmd;
else
state->request_qtail->host_scribble = (void *) cmd;
state->request_qtail = cmd;
if (state->phase == idle)
mac53c94_start(state);
restore_flags(flags);
return 0;
}
int
mac53c94_abort(Scsi_Cmnd *cmd)
{
return SCSI_ABORT_SNOOZE;
}
int
mac53c94_reset(Scsi_Cmnd *cmd, unsigned how)
{
struct fsc_state *state = (struct fsc_state *) cmd->host->hostdata;
volatile struct mac53c94_regs *regs = state->regs;
volatile struct dbdma_regs *dma = state->dma;
unsigned long flags;
save_flags(flags);
cli();
st_le32(&dma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
regs->command = CMD_SCSI_RESET; /* assert RST */
eieio();
udelay(100); /* leave it on for a while (>= 25us) */
regs->command = CMD_RESET;
eieio();
udelay(20);
mac53c94_init(state);
regs->command = CMD_NOP;
eieio();
restore_flags(flags);
return SCSI_RESET_PENDING;
}
int
mac53c94_command(Scsi_Cmnd *cmd)
{
printk(KERN_DEBUG "whoops... mac53c94_command called\n");
return -1;
}
static void
mac53c94_init(struct fsc_state *state)
{
volatile struct mac53c94_regs *regs = state->regs;
volatile struct dbdma_regs *dma = state->dma;
int x;
regs->config1 = state->host->this_id | CF1_PAR_ENABLE;
regs->sel_timeout = TIMO_VAL(250); /* 250ms */
regs->clk_factor = CLKF_VAL(state->clk_freq);
regs->config2 = CF2_FEATURE_EN;
regs->config3 = 0;
regs->sync_period = 0;
regs->sync_offset = 0;
eieio();
x = regs->interrupt;
st_le32(&dma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
}
/*
* Start the next command for a 53C94.
* Should be called with interrupts disabled.
*/
static void
mac53c94_start(struct fsc_state *state)
{
Scsi_Cmnd *cmd;
volatile struct mac53c94_regs *regs = state->regs;
int i;
if (state->phase != idle || state->current_req != NULL)
panic("inappropriate mac53c94_start (state=%p)", state);
if (state->request_q == NULL)
return;
state->current_req = cmd = state->request_q;
state->request_q = (Scsi_Cmnd *) cmd->host_scribble;
/* Off we go */
regs->count_lo = 0;
regs->count_mid = 0;
regs->count_hi = 0;
eieio();
regs->command = CMD_NOP + CMD_DMA_MODE;
udelay(1);
eieio();
regs->command = CMD_FLUSH;
udelay(1);
eieio();
regs->dest_id = cmd->target;
regs->sync_period = 0;
regs->sync_offset = 0;
eieio();
/* load the command into the FIFO */
for (i = 0; i < cmd->cmd_len; ++i) {
regs->fifo = cmd->cmnd[i];
eieio();
}
/* do select without ATN XXX */
regs->command = CMD_SELECT;
state->phase = selecting;
if (cmd->use_sg > 0 || cmd->request_bufflen != 0)
set_dma_cmds(state, cmd);
}
static void
do_mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs)
{
unsigned long flags;
struct Scsi_Host *dev = ((struct fsc_state *) dev_id)->current_req->host;
spin_lock_irqsave(dev->host_lock, flags);
mac53c94_interrupt(irq, dev_id, ptregs);
spin_unlock_irqrestore(dev->host_lock, flags);
}
static void
mac53c94_interrupt(int irq, void *dev_id, struct pt_regs *ptregs)
{
struct fsc_state *state = (struct fsc_state *) dev_id;
volatile struct mac53c94_regs *regs = state->regs;
volatile struct dbdma_regs *dma = state->dma;
Scsi_Cmnd *cmd = state->current_req;
int nb, stat, seq, intr;
static int mac53c94_errors;
/*
* Apparently, reading the interrupt register unlatches
* the status and sequence step registers.
*/
seq = regs->seqstep;
stat = regs->status;
intr = regs->interrupt;
#if 0
printk(KERN_DEBUG "mac53c94_intr, intr=%x stat=%x seq=%x phase=%d\n",
intr, stat, seq, state->phase);
#endif
if (intr & INTR_RESET) {
/* SCSI bus was reset */
printk(KERN_INFO "external SCSI bus reset detected\n");
regs->command = CMD_NOP;
st_le32(&dma->control, RUN << 16); /* stop dma */
cmd_done(state, DID_RESET << 16);
return;
}
if (intr & INTR_ILL_CMD) {
printk(KERN_ERR "53c94: illegal cmd, intr=%x stat=%x seq=%x phase=%d\n",
intr, stat, seq, state->phase);
cmd_done(state, DID_ERROR << 16);
return;
}
if (stat & STAT_ERROR) {
#if 0
/* XXX these seem to be harmless? */
printk("53c94: bad error, intr=%x stat=%x seq=%x phase=%d\n",
intr, stat, seq, state->phase);
#endif
++mac53c94_errors;
regs->command = CMD_NOP + CMD_DMA_MODE;
eieio();
}
if (cmd == 0) {
printk(KERN_DEBUG "53c94: interrupt with no command active?\n");
return;
}
if (stat & STAT_PARITY) {
printk(KERN_ERR "mac53c94: parity error\n");
cmd_done(state, DID_PARITY << 16);
return;
}
switch (state->phase) {
case selecting:
if (intr & INTR_DISCONNECT) {
/* selection timed out */
cmd_done(state, DID_BAD_TARGET << 16);
return;
}
if (intr != INTR_BUS_SERV + INTR_DONE) {
printk(KERN_DEBUG "got intr %x during selection\n", intr);
cmd_done(state, DID_ERROR << 16);
return;
}
if ((seq & SS_MASK) != SS_DONE) {
printk(KERN_DEBUG "seq step %x after command\n", seq);
cmd_done(state, DID_ERROR << 16);
return;
}
regs->command = CMD_NOP;
/* set DMA controller going if any data to transfer */
if ((stat & (STAT_MSG|STAT_CD)) == 0
&& (cmd->use_sg > 0 || cmd->request_bufflen != 0)) {
nb = cmd->SCp.this_residual;
if (nb > 0xfff0)
nb = 0xfff0;
cmd->SCp.this_residual -= nb;
regs->count_lo = nb;
regs->count_mid = nb >> 8;
eieio();
regs->command = CMD_DMA_MODE + CMD_NOP;
eieio();
st_le32(&dma->cmdptr, virt_to_phys(state->dma_cmds));
st_le32(&dma->control, (RUN << 16) | RUN);
eieio();
regs->command = CMD_DMA_MODE + CMD_XFER_DATA;
state->phase = dataing;
break;
} else if ((stat & STAT_PHASE) == STAT_CD + STAT_IO) {
/* up to status phase already */
regs->command = CMD_I_COMPLETE;
state->phase = completing;
} else {
printk(KERN_DEBUG "in unexpected phase %x after cmd\n",
stat & STAT_PHASE);
cmd_done(state, DID_ERROR << 16);
return;
}
break;
case dataing:
if (intr != INTR_BUS_SERV) {
printk(KERN_DEBUG "got intr %x before status\n", intr);
cmd_done(state, DID_ERROR << 16);
return;
}
if (cmd->SCp.this_residual != 0
&& (stat & (STAT_MSG|STAT_CD)) == 0) {
/* Set up the count regs to transfer more */
nb = cmd->SCp.this_residual;
if (nb > 0xfff0)
nb = 0xfff0;
cmd->SCp.this_residual -= nb;
regs->count_lo = nb;
regs->count_mid = nb >> 8;
eieio();
regs->command = CMD_DMA_MODE + CMD_NOP;
eieio();
regs->command = CMD_DMA_MODE + CMD_XFER_DATA;
break;
}
if ((stat & STAT_PHASE) != STAT_CD + STAT_IO) {
printk(KERN_DEBUG "intr %x before data xfer complete\n", intr);
}
st_le32(&dma->control, RUN << 16); /* stop dma */
/* should check dma status */
regs->command = CMD_I_COMPLETE;
state->phase = completing;
break;
case completing:
if (intr != INTR_DONE) {
printk(KERN_DEBUG "got intr %x on completion\n", intr);
cmd_done(state, DID_ERROR << 16);
return;
}
cmd->SCp.Status = regs->fifo; eieio();
cmd->SCp.Message = regs->fifo; eieio();
cmd->result =
regs->command = CMD_ACCEPT_MSG;
state->phase = busfreeing;
break;
case busfreeing:
if (intr != INTR_DISCONNECT) {
printk(KERN_DEBUG "got intr %x when expected disconnect\n", intr);
}
cmd_done(state, (DID_OK << 16) + (cmd->SCp.Message << 8)
+ cmd->SCp.Status);
break;
default:
printk(KERN_DEBUG "don't know about phase %d\n", state->phase);
}
}
static void
cmd_done(struct fsc_state *state, int result)
{
Scsi_Cmnd *cmd;
cmd = state->current_req;
if (cmd != 0) {
cmd->result = result;
(*cmd->scsi_done)(cmd);
state->current_req = NULL;
}
state->phase = idle;
mac53c94_start(state);
}
/*
* Set up DMA commands for transferring data.
*/
static void
set_dma_cmds(struct fsc_state *state, Scsi_Cmnd *cmd)
{
int i, dma_cmd, total;
struct scatterlist *scl;
struct dbdma_cmd *dcmds;
dma_cmd = data_goes_out(cmd)? OUTPUT_MORE: INPUT_MORE;
dcmds = state->dma_cmds;
if (cmd->use_sg > 0) {
total = 0;
scl = (struct scatterlist *) cmd->buffer;
for (i = 0; i < cmd->use_sg; ++i) {
if (scl->length > 0xffff)
panic("mac53c94: scatterlist element >= 64k");
total += scl->length;
st_le16(&dcmds->req_count, scl->length);
st_le16(&dcmds->command, dma_cmd);
st_le32(&dcmds->phy_addr, virt_to_phys(scl->address));
dcmds->xfer_status = 0;
++scl;
++dcmds;
}
} else {
total = cmd->request_bufflen;
if (total > 0xffff)
panic("mac53c94: transfer size >= 64k");
st_le16(&dcmds->req_count, total);
st_le32(&dcmds->phy_addr, virt_to_phys(cmd->request_buffer));
dcmds->xfer_status = 0;
++dcmds;
}
dma_cmd += OUTPUT_LAST - OUTPUT_MORE;
st_le16(&dcmds[-1].command, dma_cmd);
st_le16(&dcmds->command, DBDMA_STOP);
cmd->SCp.this_residual = total;
}
/*
* Work out whether data will be going out from the host adaptor or into it.
* (If this information is available from somewhere else in the scsi
* code, somebody please let me know :-)
*/
static int
data_goes_out(Scsi_Cmnd *cmd)
{
switch (cmd->cmnd[0]) {
case CHANGE_DEFINITION:
case COMPARE:
case COPY:
case COPY_VERIFY:
case FORMAT_UNIT:
case LOG_SELECT:
case MEDIUM_SCAN:
case MODE_SELECT:
case MODE_SELECT_10:
case REASSIGN_BLOCKS:
case RESERVE:
case SEARCH_EQUAL:
case SEARCH_EQUAL_12:
case SEARCH_HIGH:
case SEARCH_HIGH_12:
case SEARCH_LOW:
case SEARCH_LOW_12:
case SEND_DIAGNOSTIC:
case SEND_VOLUME_TAG:
case SET_WINDOW:
case UPDATE_BLOCK:
case WRITE_BUFFER:
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_LONG:
case WRITE_LONG_2: /* alternate code for WRITE_LONG */
case WRITE_SAME:
case WRITE_VERIFY:
case WRITE_VERIFY_12:
return 1;
default:
return 0;
}
}
static Scsi_Host_Template driver_template = SCSI_MAC53C94;
#include "scsi_module.c"