/*	$NecBSD: ct.c,v 1.13.12.5 2001/06/26 07:31:53 honda Exp $	*/

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*	$NetBSD$	*/

#define	CT_DEBUG
#define	CT_IO_CONTROL_FLAGS	(CT_USE_CCSEQ | CT_FAST_INTR)

/*-
 * [NetBSD for NEC PC-98 series]
 *  Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001
 *	NetBSD/pc98 porting staff. All rights reserved.
 * 
 *  Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001
 *	Naofumi HONDA.  All rights reserved.
 * 
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bio.h>
#include <sys/buf.h>
#include <sys/queue.h>
#include <sys/malloc.h>
#include <sys/errno.h>

#include <machine/bus.h>

#include <cam/scsi/scsi_low.h>

#include <dev/ic/wd33c93reg.h>
#include <dev/ct/ctvar.h>
#include <dev/ct/ct_machdep.h>

#define	CT_NTARGETS		8
#define	CT_NLUNS		8
#define	CT_RESET_DEFAULT	2000
#define	CT_DELAY_MAX		(2 * 1000 * 1000)
#define	CT_DELAY_INTERVAL	(1)

/***************************************************
 * DEBUG
 ***************************************************/
#ifdef	CT_DEBUG
int ct_debug;
#endif	/* CT_DEBUG */

/***************************************************
 * IO control
 ***************************************************/
#define	CT_USE_CCSEQ	0x0100
#define	CT_FAST_INTR	0x0200

u_int ct_io_control = CT_IO_CONTROL_FLAGS;

/***************************************************
 * default data
 ***************************************************/
u_int8_t cthw_cmdlevel[256] = {
/*   0  1   2   3   4   5   6   7   8   9   A   B   C   E   D   F */
/*0*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,1  ,0  ,1  ,0  ,0  ,0  ,0  ,0  ,
/*1*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*2*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,1  ,0  ,1  ,0  ,0  ,0  ,0  ,0  ,
/*3*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*4*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*5*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*6*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*7*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*8*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*9*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*A*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*B*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*C*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*D*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*E*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
/*F*/0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,0  ,
};

#if	0
/* default synch data table */
/*  A  10    6.6   5.0   4.0   3.3   2.8   2.5   2.0  M/s */
/*  X  100   150   200   250   300   350   400   500  ns  */
static struct ct_synch_data ct_synch_data_FSCSI[] = {
	{25, 0xa0}, {37, 0xb0}, {50, 0x20}, {62, 0xd0}, {75, 0x30},
	{87, 0xf0}, {100, 0x40}, {125, 0x50}, {0, 0}
};

static struct ct_synch_data ct_synch_data_SCSI[] = {
	{50, 0x20}, {75, 0x30}, {100, 0x40}, {125, 0x50}, {0, 0}
};
#endif
/***************************************************
 * DEVICE STRUCTURE
 ***************************************************/
extern struct cfdriver ct_cd;

/*****************************************************************
 * Interface functions
 *****************************************************************/
static int ct_xfer(struct ct_softc *, u_int8_t *, int, int, u_int *);
static void ct_io_xfer(struct ct_softc *);
static int ct_reselected(struct ct_softc *, u_int8_t);
static void ct_phase_error(struct ct_softc *, u_int8_t);
static int ct_start_selection(struct ct_softc *, struct slccb *);
static int ct_msg(struct ct_softc *, struct targ_info *, u_int);
static int ct_world_start(struct ct_softc *, int);
static __inline void cthw_phase_bypass(struct ct_softc *, u_int8_t);
static int cthw_chip_reset(struct ct_bus_access_handle *, int *, int, int);
static void cthw_bus_reset(struct ct_softc *);
static int ct_ccb_nexus_establish(struct ct_softc *);
static int ct_lun_nexus_establish(struct ct_softc *);
static int ct_target_nexus_establish(struct ct_softc *, int, int);
static void cthw_attention(struct ct_softc *);
static int ct_targ_init(struct ct_softc *, struct targ_info *, int);
static int ct_unbusy(struct ct_softc *);
static void ct_attention(struct ct_softc *);
static struct ct_synch_data *ct_make_synch_table(struct ct_softc *);
static int ct_catch_intr(struct ct_softc *);

struct scsi_low_funcs ct_funcs = {
	SC_LOW_INIT_T ct_world_start,
	SC_LOW_BUSRST_T cthw_bus_reset,
	SC_LOW_TARG_INIT_T ct_targ_init,
	SC_LOW_LUN_INIT_T NULL,

	SC_LOW_SELECT_T ct_start_selection,
	SC_LOW_NEXUS_T ct_lun_nexus_establish,
	SC_LOW_NEXUS_T ct_ccb_nexus_establish,

	SC_LOW_ATTEN_T cthw_attention,
	SC_LOW_MSG_T ct_msg,

	SC_LOW_TIMEOUT_T NULL,
	SC_LOW_POLL_T ctintr,

	NULL,	/* SC_LOW_POWER_T cthw_power, */
};

/**************************************************
 * HW functions
 **************************************************/
static __inline void
cthw_phase_bypass(struct ct_softc *ct, u_int8_t ph)
{
	struct ct_bus_access_handle *chp = &ct->sc_ch;

	ct_cr_write_1(chp, wd3s_cph, ph);
	ct_cr_write_1(chp, wd3s_cmd, WD3S_SELECT_ATN_TFR);
}

static void
cthw_bus_reset(struct ct_softc *ct)
{

	/*
	 * wd33c93 does not have bus reset function.
	 */
	if (ct->ct_bus_reset != NULL)
		((*ct->ct_bus_reset) (ct));
}

static int
cthw_chip_reset(struct ct_bus_access_handle *chp, int *chiprevp, int chipclk,
    int hostid)
{
#define	CT_SELTIMEOUT_20MHz_REGV	(0x80)
	u_int8_t aux, regv;
	u_int seltout;
	int wc;

	/* issue abort cmd */
	ct_cr_write_1(chp, wd3s_cmd, WD3S_ABORT);
	DELAY(1000);	/* 1ms wait */
	(void) ct_stat_read_1(chp);
	(void) ct_cr_read_1(chp, wd3s_stat);

	/* setup chip registers */
	regv = 0;
	seltout = CT_SELTIMEOUT_20MHz_REGV;
	switch (chipclk)
	{
	case 8:
	case 10:
		seltout = (seltout * chipclk) / 20;
		regv = IDR_FS_8_10;
		break;

	case 12:
	case 15:
		seltout = (seltout * chipclk) / 20;
		regv = IDR_FS_12_15;
		break;

	case 16:
	case 20:
		seltout = (seltout * chipclk) / 20;
		regv = IDR_FS_16_20;
		break;

	default:
		panic("ct: illegal chip clk rate");
		break;
	}

	regv |= IDR_EHP | hostid | IDR_RAF | IDR_EAF;
	ct_cr_write_1(chp, wd3s_oid, regv);

	ct_cr_write_1(chp, wd3s_cmd, WD3S_RESET);
	for (wc = CT_RESET_DEFAULT; wc > 0; wc --)
	{
		aux = ct_stat_read_1(chp);
		if (aux != 0xff && (aux & STR_INT))
		{
			regv = ct_cr_read_1(chp, wd3s_stat);
			if (regv == BSR_RESET || regv == BSR_AFM_RESET)
				break;

			ct_cr_write_1(chp, wd3s_cmd, WD3S_RESET);
		}
		DELAY(1);
	}
	if (wc == 0)
		return ENXIO;

	ct_cr_write_1(chp, wd3s_tout, seltout);
	ct_cr_write_1(chp, wd3s_sid, SIDR_RESEL);
	ct_cr_write_1(chp, wd3s_ctrl, CR_DEFAULT);
	ct_cr_write_1(chp, wd3s_synch, 0);
	if (chiprevp != NULL)
	{
		*chiprevp = CT_WD33C93;
		if (regv == BSR_RESET)
			goto out;

		*chiprevp = CT_WD33C93_A;
		ct_cr_write_1(chp, wd3s_qtag, 0xaa);
		if (ct_cr_read_1(chp, wd3s_qtag) != 0xaa)
		{
			ct_cr_write_1(chp, wd3s_qtag, 0x0);
			goto out;
		}
		ct_cr_write_1(chp, wd3s_qtag, 0x55);
		if (ct_cr_read_1(chp, wd3s_qtag) != 0x55)
		{
			ct_cr_write_1(chp, wd3s_qtag, 0x0);
			goto out;
		}
		ct_cr_write_1(chp, wd3s_qtag, 0x0);
		*chiprevp = CT_WD33C93_B;
	}

out:
	(void) ct_stat_read_1(chp);
	(void) ct_cr_read_1(chp, wd3s_stat);
	return 0;
}

static struct ct_synch_data *
ct_make_synch_table(struct ct_softc *ct)
{
	struct ct_synch_data *sdtp, *sdp;
	u_int base, i, period;

	sdtp = sdp = &ct->sc_default_sdt[0];

	if ((ct->sc_chipclk % 5) == 0)
		base = 1000 / (5 * 2);	/* 5 MHz type */
	else
		base = 1000 / (4 * 2);	/* 4 MHz type */

	if (ct->sc_chiprev >= CT_WD33C93_B)
	{
		/* fast scsi */
		for (i = 2; i < 8; i ++, sdp ++)
		{
			period = (base * i) / 2;
			if (period >= 200)	/* 5 MHz */
				break;
			sdp->cs_period = period / 4;
			sdp->cs_syncr = (i * 0x10) | 0x80;
		}
	}

	for (i = 2; i < 8; i ++, sdp ++)
	{
		period = (base * i);
		if (period > 500)		/* 2 MHz */
			break;
		sdp->cs_period = period / 4;
		sdp->cs_syncr = (i * 0x10);
	}

	sdp->cs_period = 0;
	sdp->cs_syncr = 0;
	return sdtp;
}

/**************************************************
 * Attach & Probe
 **************************************************/
int
ctprobesubr(struct ct_bus_access_handle *chp, u_int dvcfg, int hsid,
    u_int chipclk, int *chiprevp)
{

#if	0
	if ((ct_stat_read_1(chp) & STR_BSY) != 0)
		return 0;
#endif
	if (cthw_chip_reset(chp, chiprevp, chipclk, hsid) != 0)
		return 0;
	return 1;
}

void
ctattachsubr(struct ct_softc *ct)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;

	ct->sc_tmaxcnt = SCSI_LOW_MIN_TOUT * 1000 * 1000; /* default */
	slp->sl_funcs = &ct_funcs;
	slp->sl_flags |= HW_READ_PADDING;
	(void) scsi_low_attach(slp, 0, CT_NTARGETS, CT_NLUNS,
			       sizeof(struct ct_targ_info), 0);
}

/**************************************************
 * SCSI LOW interface functions
 **************************************************/
static void
cthw_attention(struct ct_softc *ct)
{
	struct ct_bus_access_handle *chp = &ct->sc_ch;

	ct->sc_atten = 1;
	if ((ct_stat_read_1(chp) & (STR_BSY | STR_CIP)) != 0)
		return;

	ct_cr_write_1(chp, wd3s_cmd, WD3S_ASSERT_ATN);
	DELAY(10);
	if ((ct_stat_read_1(chp) & STR_LCI) == 0)
		ct->sc_atten = 0;
	ct_unbusy(ct);
	return;
}

static void
ct_attention(struct ct_softc *ct)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;

	if (slp->sl_atten == 0)
	{
		ct_unbusy(ct);
		scsi_low_attention(slp);
	}
	else if (ct->sc_atten != 0)
	{
		ct_unbusy(ct);
		cthw_attention(ct);
	}
}

static int
ct_targ_init(struct ct_softc *ct, struct targ_info *ti, int action)
{
	struct ct_targ_info *cti = (void *) ti;

	if (action == SCSI_LOW_INFO_ALLOC || action == SCSI_LOW_INFO_REVOKE)
	{
		if (ct->sc_sdp == NULL)
		{
			ct->sc_sdp = ct_make_synch_table(ct);
		}

		switch (ct->sc_chiprev)
		{
		default:
			ti->ti_maxsynch.offset = 5;
			break;

		case CT_WD33C93_A:
		case CT_AM33C93_A:
			ti->ti_maxsynch.offset = 12;
			break;

		case CT_WD33C93_B:
		case CT_WD33C93_C:
			ti->ti_maxsynch.offset = 12;
			break;
		}

		ti->ti_maxsynch.period = ct->sc_sdp[0].cs_period;
		ti->ti_width = SCSI_LOW_BUS_WIDTH_8;
		cti->cti_syncreg = 0;
	}

	return 0;
}	

static int
ct_world_start(struct ct_softc *ct, int fdone)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct ct_bus_access_handle *chp = &ct->sc_ch;

	if (ct->sc_sdp == NULL)
	{
		ct->sc_sdp = ct_make_synch_table(ct);
	}

	if (slp->sl_cfgflags & CFG_NOPARITY)
		ct->sc_creg = CR_DEFAULT;
	else
		ct->sc_creg = CR_DEFAULT_HP;

	if (ct->sc_dma & CT_DMA_DMASTART)
		(*ct->ct_dma_xfer_stop) (ct);
	if (ct->sc_dma & CT_DMA_PIOSTART)
		(*ct->ct_pio_xfer_stop) (ct);
	ct->sc_dma = 0;
	ct->sc_atten = 0;

	cthw_chip_reset(chp, NULL, ct->sc_chipclk, slp->sl_hostid);
	scsi_low_bus_reset(slp);
	cthw_chip_reset(chp, NULL, ct->sc_chipclk, slp->sl_hostid);

	return 0;
}

static int
ct_start_selection(struct ct_softc *ct, struct slccb *cb)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct ct_bus_access_handle *chp = &ct->sc_ch;

	struct targ_info *ti = slp->sl_Tnexus;
	struct lun_info *li = slp->sl_Lnexus;
	int s, satok;
	u_int8_t cmd;

	ct->sc_tmaxcnt = cb->ccb_tcmax * 1000 * 1000;
	ct->sc_atten = 0;
	satok = 0;

	if (scsi_low_is_disconnect_ok(cb) != 0)
	{	
		if (ct->sc_chiprev >= CT_WD33C93_A)
			satok = 1;
		else if (cthw_cmdlevel[slp->sl_scp.scp_cmd[0]] != 0)
			satok = 1;
	}

	if (satok != 0 &&
	    scsi_low_is_msgout_continue(ti, SCSI_LOW_MSG_IDENTIFY) == 0)
	{
		cmd = WD3S_SELECT_ATN_TFR;
		ct->sc_satgo = CT_SAT_GOING;
	}
	else
	{
		cmd = WD3S_SELECT_ATN;
		ct->sc_satgo = 0;
	}

	if ((ct_stat_read_1(chp) & (STR_BSY | STR_INT | STR_CIP)) != 0)
		return SCSI_LOW_START_FAIL;

	if ((ct->sc_satgo & CT_SAT_GOING) != 0)
	{
		(void) scsi_low_msgout(slp, ti, SCSI_LOW_MSGOUT_INIT);
		scsi_low_cmd(slp, ti);
		ct_cr_write_1(chp, wd3s_oid, slp->sl_scp.scp_cmdlen);
		ct_write_cmds(chp, slp->sl_scp.scp_cmd, slp->sl_scp.scp_cmdlen);
	}
	else
	{
		/* anyway attention assert */
		SCSI_LOW_ASSERT_ATN(slp);
	}

	ct_target_nexus_establish(ct, li->li_lun, slp->sl_scp.scp_direction);

	s = splhigh();
	if ((ct_stat_read_1(chp) & (STR_BSY | STR_INT | STR_CIP)) == 0)
	{
		/* XXX: 
		 * Reload a lun again here.
		 */
		ct_cr_write_1(chp, wd3s_lun, li->li_lun);
		ct_cr_write_1(chp, wd3s_cmd, cmd);
		if ((ct_stat_read_1(chp) & STR_LCI) == 0)
		{
			splx(s);
			SCSI_LOW_SETUP_PHASE(ti, PH_SELSTART);
			return SCSI_LOW_START_OK;
		}
	}
	splx(s);
	return SCSI_LOW_START_FAIL;
}

static int
ct_msg(struct ct_softc *ct, struct targ_info *ti, u_int msg)
{
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	struct ct_targ_info *cti = (void *) ti;
	struct ct_synch_data *csp = ct->sc_sdp;
	u_int offset, period;
	int error;

	if ((msg & SCSI_LOW_MSG_WIDE) != 0)
	{
		if (ti->ti_width != SCSI_LOW_BUS_WIDTH_8)
		{
			ti->ti_width = SCSI_LOW_BUS_WIDTH_8;
			return EINVAL;
		}
		return 0;
	}

	if ((msg & SCSI_LOW_MSG_SYNCH) == 0)
		return 0;

	offset = ti->ti_maxsynch.offset;
	period = ti->ti_maxsynch.period;
	for ( ; csp->cs_period != 0; csp ++)
	{
		if (period == csp->cs_period)
			break;
	}

	if (ti->ti_maxsynch.period != 0 && csp->cs_period == 0)
	{
		ti->ti_maxsynch.period = 0;
		ti->ti_maxsynch.offset = 0;
		cti->cti_syncreg = 0;
		error = EINVAL;
	}
	else
	{
		cti->cti_syncreg = ((offset & 0x0f) | csp->cs_syncr);
		error = 0;
	}

	if (ct->ct_synch_setup != 0)
		(*ct->ct_synch_setup) (ct, ti);
	ct_cr_write_1(chp, wd3s_synch, cti->cti_syncreg);
	return error;
}

/*************************************************
 * <DATA PHASE>
 *************************************************/
static int
ct_xfer(struct ct_softc *ct, u_int8_t *data, int len, int direction,
    u_int *statp)
{
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	int wc;
	register u_int8_t aux;

	*statp = 0;
	if (len == 1)
	{
		ct_cr_write_1(chp, wd3s_cmd, WD3S_SBT | WD3S_TFR_INFO);
	}
	else
	{
		cthw_set_count(chp, len);
		ct_cr_write_1(chp, wd3s_cmd, WD3S_TFR_INFO);
	}

	aux = ct_stat_read_1(chp);
	if ((aux & STR_LCI) != 0)
	{
		cthw_set_count(chp, 0);
		return len;
	}

	for (wc = 0; wc < ct->sc_tmaxcnt; wc ++)
	{
		/* check data ready */
		if ((aux & (STR_BSY | STR_DBR)) == (STR_BSY | STR_DBR))
		{
			if (direction == SCSI_LOW_READ)
			{
				*data = ct_cr_read_1(chp, wd3s_data);
				if ((aux & STR_PE) != 0)
					*statp |= SCSI_LOW_DATA_PE;
			}
			else
			{
				ct_cr_write_1(chp, wd3s_data, *data);
			}
			len --;
			if (len <= 0)
				break;
			data ++;
		}
		else
		{
			DELAY(1);
		}

		/* check phase miss */
		aux = ct_stat_read_1(chp);
		if ((aux & STR_INT) != 0)
			break;
	}
	return len;
}

#define	CT_PADDING_BUF_SIZE 32

static void
ct_io_xfer(struct ct_softc *ct)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	struct sc_p *sp = &slp->sl_scp;
	u_int stat;
	int len;
	u_int8_t pbuf[CT_PADDING_BUF_SIZE];

	/* polling mode */
	ct_cr_write_1(chp, wd3s_ctrl, ct->sc_creg);

	if (sp->scp_datalen <= 0)
	{
		slp->sl_error |= PDMAERR;

		if (slp->sl_scp.scp_direction == SCSI_LOW_WRITE)
			bzero(pbuf, CT_PADDING_BUF_SIZE);
		ct_xfer(ct, pbuf, CT_PADDING_BUF_SIZE, 
			sp->scp_direction, &stat);
	}
	else
	{
		len = ct_xfer(ct, sp->scp_data, sp->scp_datalen,
			      sp->scp_direction, &stat);
		sp->scp_data += (sp->scp_datalen - len);
		sp->scp_datalen = len;
	}
}

/**************************************************
 * <PHASE ERROR>
 **************************************************/
struct ct_err {
	u_char	*pe_msg;
	u_int	pe_err;
	u_int	pe_errmsg;
	int	pe_done;
};

struct ct_err ct_cmderr[] = {
/*0*/	{ "illegal cmd", FATALIO, SCSI_LOW_MSG_ABORT, 1},
/*1*/	{ "unexpected bus free", FATALIO, 0, 1},
/*2*/	{ NULL, SELTIMEOUTIO, 0, 1},
/*3*/	{ "scsi bus parity error", PARITYERR, SCSI_LOW_MSG_ERROR, 0},
/*4*/	{ "scsi bus parity error", PARITYERR, SCSI_LOW_MSG_ERROR, 0},
/*5*/	{ "unknown" , FATALIO, SCSI_LOW_MSG_ABORT, 1},
/*6*/	{ "miss reselection (target mode)", FATALIO, SCSI_LOW_MSG_ABORT, 0},
/*7*/	{ "wrong status byte", PARITYERR, SCSI_LOW_MSG_ERROR, 0},
};

static void
ct_phase_error(struct ct_softc *ct, u_int8_t scsi_status)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct targ_info *ti = slp->sl_Tnexus;
	struct ct_err *pep;
	u_int msg = 0;

	if ((scsi_status & BSR_CM) == BSR_CMDERR &&
	    (scsi_status & BSR_PHVALID) == 0)
	{
		pep = &ct_cmderr[scsi_status & BSR_PM];
		slp->sl_error |= pep->pe_err;
		if ((pep->pe_err & PARITYERR) != 0)
		{
			if (ti->ti_phase == PH_MSGIN)
				msg = SCSI_LOW_MSG_PARITY;
			else
				msg = SCSI_LOW_MSG_ERROR;
		}
		else
			msg = pep->pe_errmsg;	

		if (msg != 0)
			scsi_low_assert_msg(slp, slp->sl_Tnexus, msg, 1);

		if (pep->pe_msg != NULL)
		{
			device_printf(slp->sl_dev, "phase error: %s",
			    pep->pe_msg);
			scsi_low_print(slp, slp->sl_Tnexus);
		}

		if (pep->pe_done != 0)
			scsi_low_disconnected(slp, ti);
	}
	else
	{
		slp->sl_error |= FATALIO;
		scsi_low_restart(slp, SCSI_LOW_RESTART_HARD, "phase error");
	}
}

/**************************************************
 * ### SCSI PHASE SEQUENCER ###
 **************************************************/
static int
ct_reselected(struct ct_softc *ct, u_int8_t scsi_status)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	struct targ_info *ti;
	u_int sid;
	u_int8_t regv;

	ct->sc_atten = 0;
	ct->sc_satgo &= ~CT_SAT_GOING;
	regv = ct_cr_read_1(chp, wd3s_sid);
	if ((regv & SIDR_VALID) == 0)
		return EJUSTRETURN;

	sid = regv & SIDR_IDM;
	if ((ti = scsi_low_reselected(slp, sid)) == NULL)
		return EJUSTRETURN;

	ct_target_nexus_establish(ct, 0, SCSI_LOW_READ);
	if (scsi_status != BSR_AFM_RESEL)
		return EJUSTRETURN;

	SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN);
	regv = ct_cr_read_1(chp, wd3s_data);
	if (scsi_low_msgin(slp, ti, (u_int) regv) == 0)
	{
		if (scsi_low_is_msgout_continue(ti, 0) != 0)
		{
			/* XXX: scsi_low_attetion */
			scsi_low_attention(slp);
		}
	}

	if (ct->sc_atten != 0)
	{
		ct_attention(ct);
	}

	ct_cr_write_1(chp, wd3s_cmd, WD3S_NEGATE_ACK);
	return EJUSTRETURN;
}

static int
ct_target_nexus_establish(struct ct_softc *ct, int lun, int dir)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	struct targ_info *ti = slp->sl_Tnexus;
	struct ct_targ_info *cti = (void *) ti;

	if (dir == SCSI_LOW_WRITE)
		ct_cr_write_1(chp, wd3s_did, ti->ti_id);
	else
		ct_cr_write_1(chp, wd3s_did, ti->ti_id | DIDR_DPD); 
	ct_cr_write_1(chp, wd3s_lun, lun);
	ct_cr_write_1(chp, wd3s_ctrl, ct->sc_creg | CR_DMA);
	ct_cr_write_1(chp, wd3s_cph, 0);
	ct_cr_write_1(chp, wd3s_synch, cti->cti_syncreg);
	cthw_set_count(chp, 0);
	return 0;
}

static int
ct_lun_nexus_establish(struct ct_softc *ct)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	struct lun_info *li = slp->sl_Lnexus;

	ct_cr_write_1(chp, wd3s_lun, li->li_lun);
	return 0;
}

static int
ct_ccb_nexus_establish(struct ct_softc *ct)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	struct lun_info *li = slp->sl_Lnexus;
	struct targ_info *ti = slp->sl_Tnexus;
	struct ct_targ_info *cti = (void *) ti;
	struct slccb *cb = slp->sl_Qnexus;

	ct->sc_tmaxcnt = cb->ccb_tcmax * 1000 * 1000;

	if ((ct->sc_satgo & CT_SAT_GOING) != 0)
	{
		ct_cr_write_1(chp, wd3s_oid, slp->sl_scp.scp_cmdlen);
		ct_write_cmds(chp, slp->sl_scp.scp_cmd, slp->sl_scp.scp_cmdlen);
	}
	if (slp->sl_scp.scp_direction == SCSI_LOW_WRITE)
		ct_cr_write_1(chp, wd3s_did, ti->ti_id);
	else
		ct_cr_write_1(chp, wd3s_did, ti->ti_id | DIDR_DPD);
	ct_cr_write_1(chp, wd3s_lun, li->li_lun);
	ct_cr_write_1(chp, wd3s_synch, cti->cti_syncreg);
	return 0;
}

static int
ct_unbusy(struct ct_softc *ct)
{
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	int wc;
	register u_int8_t regv;

	for (wc = 0; wc < CT_DELAY_MAX / CT_DELAY_INTERVAL; wc ++)
	{
		regv = ct_stat_read_1(chp);
		if ((regv & (STR_BSY | STR_CIP)) == 0)
			return 0;
		if (regv == (u_int8_t) -1)
			return EIO;

		DELAY(CT_DELAY_INTERVAL);
	}

	device_printf(slp->sl_dev, "unbusy timeout\n");
	return EBUSY;
}
	
static int
ct_catch_intr(struct ct_softc *ct)
{
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	int wc;
	register u_int8_t regv;

	for (wc = 0; wc < CT_DELAY_MAX / CT_DELAY_INTERVAL; wc ++)
	{
		regv = ct_stat_read_1(chp);
		if ((regv & (STR_INT | STR_BSY | STR_CIP)) == STR_INT)
			return 0;

		DELAY(CT_DELAY_INTERVAL);
	}
	return EJUSTRETURN;
}

int
ctintr(void *arg)
{
	struct ct_softc *ct = arg;
	struct scsi_low_softc *slp = &ct->sc_sclow;
	struct ct_bus_access_handle *chp = &ct->sc_ch;
	struct targ_info *ti;
	struct buf *bp;
	u_int derror, flags;
	int len, satgo, error;
	u_int8_t scsi_status, regv;

again:
	if (slp->sl_flags & HW_INACTIVE)
		return 0;

	/**************************************************
	 * Get status & bus phase
	 **************************************************/
	if ((ct_stat_read_1(chp) & STR_INT) == 0)
		return 0;

	scsi_status = ct_cr_read_1(chp, wd3s_stat);
	if (scsi_status == ((u_int8_t) -1))
		return 1;

	/**************************************************
	 * Check reselection, or nexus
	 **************************************************/
	if (scsi_status == BSR_RESEL || scsi_status == BSR_AFM_RESEL)
	{
		if (ct_reselected(ct, scsi_status) == EJUSTRETURN)
			return 1;
	}

	if ((ti = slp->sl_Tnexus) == NULL)
		return 1;

	/**************************************************
	 * Debug section
	 **************************************************/
#ifdef	CT_DEBUG
	if (ct_debug > 0)
	{
		scsi_low_print(slp, NULL);
		device_printf(slp->sl_dev, "scsi_status 0x%x\n\n", 
		       (u_int) scsi_status);
#ifdef	KDB
		if (ct_debug > 1)
			kdb_enter(KDB_WHY_CAM, "ct");
#endif	/* KDB */
	}
#endif	/* CT_DEBUG */

	/**************************************************
	 * Internal scsi phase
	 **************************************************/
	satgo = ct->sc_satgo;
	ct->sc_satgo &= ~CT_SAT_GOING;

	switch (ti->ti_phase)
	{
	case PH_SELSTART:
		if ((satgo & CT_SAT_GOING) == 0)
		{
			if (scsi_status != BSR_SELECTED)
			{
				ct_phase_error(ct, scsi_status);
				return 1;
			}
			scsi_low_arbit_win(slp);
			SCSI_LOW_SETUP_PHASE(ti, PH_SELECTED);
			return 1;
		}
		else
		{
			scsi_low_arbit_win(slp);
			SCSI_LOW_SETUP_PHASE(ti, PH_MSGOUT); /* XXX */
		}
		break;

	case PH_RESEL:
	    	if ((scsi_status & BSR_PHVALID) == 0 ||
		    (scsi_status & BSR_PM) != BSR_MSGIN)
		{
			scsi_low_restart(slp, SCSI_LOW_RESTART_HARD, 
				 "phase miss after reselect");
			return 1;
		}
		break;

	default:
		if (slp->sl_flags & HW_PDMASTART)
		{
			slp->sl_flags &= ~HW_PDMASTART;
			if (ct->sc_dma & CT_DMA_DMASTART)
			{
				(*ct->ct_dma_xfer_stop) (ct);
				ct->sc_dma &= ~CT_DMA_DMASTART;
			}
			else if (ct->sc_dma & CT_DMA_PIOSTART)
			{
				(*ct->ct_pio_xfer_stop) (ct);
				ct->sc_dma &= ~CT_DMA_PIOSTART;
			}
			else
			{
				scsi_low_data_finish(slp);
			}
		}
		break;
	}

	/**************************************************
	 * parse scsi phase
	 **************************************************/
	if (scsi_status & BSR_PHVALID)
	{
		/**************************************************
		 * Normal SCSI phase.
		 **************************************************/
		if ((scsi_status & BSR_CM) == BSR_CMDABT)
		{
			ct_phase_error(ct, scsi_status);
			return 1;
		}

		switch (scsi_status & BSR_PM)
		{
		case BSR_DATAOUT:
			SCSI_LOW_SETUP_PHASE(ti, PH_DATA);
			if (scsi_low_data(slp, ti, &bp, SCSI_LOW_WRITE) != 0)
			{
				ct_attention(ct);
			}
			goto common_data_phase;

		case BSR_DATAIN:
			SCSI_LOW_SETUP_PHASE(ti, PH_DATA);
			if (scsi_low_data(slp, ti, &bp, SCSI_LOW_READ) != 0)
			{
				ct_attention(ct);
			}

common_data_phase:
			if (slp->sl_scp.scp_datalen > 0)
			{
				slp->sl_flags |= HW_PDMASTART;
				if ((ct->sc_xmode & CT_XMODE_PIO) != 0)
				{
					error = (*ct->ct_pio_xfer_start) (ct);
					if (error == 0)
					{
						ct->sc_dma |= CT_DMA_PIOSTART;
						return 1;
					}
				}

				if ((ct->sc_xmode & CT_XMODE_DMA) != 0)
				{
					error = (*ct->ct_dma_xfer_start) (ct);
					if (error == 0)
					{
						ct->sc_dma |= CT_DMA_DMASTART;
						return 1;
					}
				}
			}
			else
			{	
				if (slp->sl_scp.scp_direction == SCSI_LOW_READ)
				{
					if (!(slp->sl_flags & HW_READ_PADDING))
					{
						device_printf(slp->sl_dev,
						    "read padding required\n");
						return 1;
					}
				}
				else
				{
					if (!(slp->sl_flags & HW_WRITE_PADDING))
					{
						device_printf(slp->sl_dev,
						    "write padding required\n");
						return 1;
					}
				}
				slp->sl_flags |= HW_PDMASTART;
			}

			ct_io_xfer(ct);
			return 1;

		case BSR_CMDOUT:
			SCSI_LOW_SETUP_PHASE(ti, PH_CMD);
			if (scsi_low_cmd(slp, ti) != 0)
			{
				ct_attention(ct);
			}

			if (ct_xfer(ct, slp->sl_scp.scp_cmd,
				    slp->sl_scp.scp_cmdlen,
				    SCSI_LOW_WRITE, &derror) != 0)
			{
				device_printf(slp->sl_dev,
				    "scsi cmd xfer short\n");
			}
			return 1;

		case BSR_STATIN:
			SCSI_LOW_SETUP_PHASE(ti, PH_STAT);
			if ((ct_io_control & CT_USE_CCSEQ) != 0)
			{
				if (scsi_low_is_msgout_continue(ti, 0) != 0 ||
				    ct->sc_atten != 0)
				{
					ct_xfer(ct, &regv, 1, SCSI_LOW_READ,
						&derror);
					scsi_low_statusin(slp, ti,
						  	  regv | derror);
				}
				else
				{
					ct->sc_satgo |= CT_SAT_GOING;
					cthw_set_count(chp, 0);
					cthw_phase_bypass(ct, 0x41);
				}
			}
			else
			{
				ct_xfer(ct, &regv, 1, SCSI_LOW_READ, &derror);
				scsi_low_statusin(slp, ti, regv | derror);
			}
			return 1;

		case BSR_UNSPINFO0:
		case BSR_UNSPINFO1:
			device_printf(slp->sl_dev, "illegal bus phase (0x%x)\n",
				(u_int) scsi_status);
			scsi_low_print(slp, ti);
			return 1;

		case BSR_MSGOUT:
			SCSI_LOW_SETUP_PHASE(ti, PH_MSGOUT);
			flags = SCSI_LOW_MSGOUT_UNIFY;
		        if (ti->ti_ophase != ti->ti_phase)
				flags |= SCSI_LOW_MSGOUT_INIT;
			len = scsi_low_msgout(slp, ti, flags);

			if (len > 1 && slp->sl_atten == 0)
			{
				ct_attention(ct);
			}

			if (ct_xfer(ct, ti->ti_msgoutstr, len, 
				    SCSI_LOW_WRITE, &derror) != 0)
			{
				device_printf(slp->sl_dev,
				    "scsi msgout xfer short\n");
			}
			SCSI_LOW_DEASSERT_ATN(slp);
			ct->sc_atten = 0;
			return 1;

		case BSR_MSGIN:/* msg in */
			SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN);

			ct_xfer(ct, &regv, 1, SCSI_LOW_READ, &derror);
			if (scsi_low_msgin(slp, ti, regv | derror) == 0)
			{
				if (scsi_low_is_msgout_continue(ti, 0) != 0)
				{
					/* XXX: scsi_low_attetion */
					scsi_low_attention(slp);
				}
			}

			if ((ct_io_control & CT_FAST_INTR) != 0)
			{
				if (ct_catch_intr(ct) == 0)
					goto again;
			}
			return 1;
		}
	}
	else
	{
		/**************************************************
		 * Special SCSI phase
		 **************************************************/
		switch (scsi_status)
		{
		case BSR_SATSDP: /* SAT with save data pointer */
			SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN);
			ct->sc_satgo |= CT_SAT_GOING;
			scsi_low_msgin(slp, ti, MSG_SAVESP);
			cthw_phase_bypass(ct, 0x41);
			return 1;

		case BSR_SATFIN: /* SAT COMPLETE */
			/*
			 * emulate statusin => msgin
			 */
			SCSI_LOW_SETUP_PHASE(ti, PH_STAT);
			scsi_low_statusin(slp, ti, ct_cr_read_1(chp, wd3s_lun));

			SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN);
			scsi_low_msgin(slp, ti, MSG_COMP);

			scsi_low_disconnected(slp, ti);
			return 1;

		case BSR_ACKREQ: /* negate ACK */
			if (ct->sc_atten != 0)
			{
				ct_attention(ct);
			}

			ct_cr_write_1(chp, wd3s_cmd, WD3S_NEGATE_ACK);
			if ((ct_io_control & CT_FAST_INTR) != 0)
			{
				/* XXX:
				 * Should clear a pending interrupt and
				 * sync with a next interrupt!
				 */
				ct_catch_intr(ct);
			}
			return 1;

		case BSR_DISC: /* disconnect */
			if (slp->sl_msgphase == MSGPH_NULL &&
			    (satgo & CT_SAT_GOING) != 0)
			{
				/*
				 * emulate disconnect msg
				 */
				SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN);
				scsi_low_msgin(slp, ti, MSG_DISCON);
			}	
			scsi_low_disconnected(slp, ti);
			return 1;

		default:
			break;
		}
	}

	ct_phase_error(ct, scsi_status);
	return 1;
}
