/*
 * 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"
