freebsd-dev/sys/dev/ncv/ncr53c500.c
John Baldwin a92cf726f8 Lock the scsi_low code and the drivers which use it along with other
related cleanups:
- Require each driver to initalize a mutex in the scsi_low_softc that
  is shared with the scsi_low code.  This mutex is used for CAM SIMs,
  timers, and interrupt handlers.
- Replace the osdep function switch with direct calls to the relevant
  CAM functions and direct manipulation of timers via callout(9).
- Collapse the CAM-specific scsi_low_osdep_interface substructure
  directly into scsi_low_softc.
- Use bus_*() instead of bus_space_*().
- Return BUS_PROBE_DEFAULT from probe routines instead of 0.
- No need to zero softcs.
- Pass 0ul and ~0ul instead of 0 and ~0 to bus_alloc_resource().
- Spell "dettach" as "detach".
- Remove unused 'dvname' variables.
- De-spl().

Tested by:	no one
2014-11-20 20:50:05 +00:00

1239 lines
28 KiB
C

/* $NecBSD: ncr53c500.c,v 1.30.12.3 2001/06/26 07:31:41 honda Exp $ */
/* $NetBSD$ */
#define NCV_DEBUG
#define NCV_STATICS
#define NCV_IO_CONTROL_FLAGS (0)
/*-
* [NetBSD for NEC PC-98 series]
* Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001
* NetBSD/pc98 porting staff. All rights reserved.
* Copyright (c) 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/cdefs.h>
__FBSDID("$FreeBSD$");
#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 <sys/rman.h>
#include <machine/cpu.h>
#include <machine/bus.h>
#include <compat/netbsd/dvcfg.h>
#include <cam/scsi/scsi_low.h>
#include <dev/ncv/ncr53c500reg.h>
#include <dev/ncv/ncr53c500hw.h>
#include <dev/ncv/ncr53c500var.h>
#include <dev/ncv/ncr53c500hwtab.h>
#define NCV_MAX_DATA_SIZE (64 * 1024)
#define NCV_DELAY_MAX (2 * 1000 * 1000)
#define NCV_DELAY_INTERVAL (1)
#define NCV_PADDING_SIZE (32)
/***************************************************
* IO control
***************************************************/
#define NCV_READ_INTERRUPTS_DRIVEN 0x0001
#define NCV_WRITE_INTERRUPTS_DRIVEN 0x0002
#define NCV_ENABLE_FAST_SCSI 0x0010
#define NCV_FAST_INTERRUPTS 0x0100
u_int ncv_io_control = NCV_IO_CONTROL_FLAGS;
int ncv_data_read_bytes = 4096;
int ncv_data_write_bytes = 4096;
/***************************************************
* DEBUG
***************************************************/
#ifdef NCV_DEBUG
static int ncv_debug;
#endif /* NCV_DEBUG */
#ifdef NCV_STATICS
static struct ncv_statics {
int disconnect;
int reselect;
} ncv_statics;
#endif /* NCV_STATICS */
/***************************************************
* DEVICE STRUCTURE
***************************************************/
extern struct cfdriver ncv_cd;
/**************************************************************
* DECLARE
**************************************************************/
/* static */
static void ncv_pio_read(struct ncv_softc *, u_int8_t *, u_int);
static void ncv_pio_write(struct ncv_softc *, u_int8_t *, u_int);
static int ncv_msg(struct ncv_softc *, struct targ_info *, u_int);
static int ncv_reselected(struct ncv_softc *);
static int ncv_disconnected(struct ncv_softc *, struct targ_info *);
static __inline void ncvhw_set_count(struct resource *, int);
static __inline u_int ncvhw_get_count(struct resource *);
static __inline void ncvhw_select_register_0(struct resource *, struct ncv_hw *);
static __inline void ncvhw_select_register_1(struct resource *, struct ncv_hw *);
static __inline void ncvhw_fpush(struct resource *, u_int8_t *, int);
static void ncv_pdma_end(struct ncv_softc *sc, struct targ_info *);
static int ncv_world_start(struct ncv_softc *, int);
static void ncvhw_bus_reset(struct ncv_softc *);
static void ncvhw_reset(struct resource *, struct ncv_hw *);
static int ncvhw_check(struct resource *, struct ncv_hw *);
static void ncvhw_init(struct resource *, struct ncv_hw *);
static int ncvhw_start_selection(struct ncv_softc *sc, struct slccb *);
static void ncvhw_attention(struct ncv_softc *);
static int ncv_ccb_nexus_establish(struct ncv_softc *);
static int ncv_lun_nexus_establish(struct ncv_softc *);
static int ncv_target_nexus_establish(struct ncv_softc *);
static int ncv_targ_init(struct ncv_softc *, struct targ_info *, int);
static int ncv_catch_intr(struct ncv_softc *);
#ifdef NCV_POWER_CONTROL
static int ncvhw_power(struct ncv_softc *, u_int);
#endif /* NCV_POWER_CONTROL */
static __inline void ncv_setup_and_start_pio(struct ncv_softc *, u_int);
struct scsi_low_funcs ncv_funcs = {
SC_LOW_INIT_T ncv_world_start,
SC_LOW_BUSRST_T ncvhw_bus_reset,
SC_LOW_TARG_INIT_T ncv_targ_init,
SC_LOW_LUN_INIT_T NULL,
SC_LOW_SELECT_T ncvhw_start_selection,
SC_LOW_NEXUS_T ncv_lun_nexus_establish,
SC_LOW_NEXUS_T ncv_ccb_nexus_establish,
SC_LOW_ATTEN_T ncvhw_attention,
SC_LOW_MSG_T ncv_msg,
SC_LOW_TIMEOUT_T NULL,
SC_LOW_POLL_T ncvintr,
NULL, /* SC_LOW_POWER_T ncvhw_power, */
};
/**************************************************************
* hwfuncs
**************************************************************/
static __inline void
ncvhw_select_register_0(struct resource *res, struct ncv_hw *hw)
{
bus_write_1(res, cr0_cfg4, hw->hw_cfg4);
}
static __inline void
ncvhw_select_register_1(struct resource *res, struct ncv_hw *hw)
{
bus_write_1(res, cr1_cfg5, hw->hw_cfg5);
}
static __inline void
ncvhw_fpush(struct resource *res, u_int8_t *buf, int len)
{
int ptr;
for (ptr = 0; ptr < len; ptr ++)
bus_write_1(res, cr0_sfifo, buf[ptr]);
}
static __inline void
ncvhw_set_count(struct resource *res, int count)
{
bus_write_1(res, cr0_tclsb, (u_int8_t) count);
bus_write_1(res, cr0_tcmsb, (u_int8_t) (count >> NBBY));
bus_write_1(res, cr0_tchsb, (u_int8_t) (count >> (NBBY * 2)));
}
static __inline u_int
ncvhw_get_count(struct resource *res)
{
u_int count;
count = (u_int) bus_read_1(res, cr0_tclsb);
count |= ((u_int) bus_read_1(res, cr0_tcmsb)) << NBBY;
count |= ((u_int) bus_read_1(res, cr0_tchsb)) << (NBBY * 2);
return count;
}
static int
ncvhw_check(struct resource *res, struct ncv_hw *hw)
{
u_int8_t stat;
ncvhw_select_register_0(res, hw);
bus_write_1(res, cr0_cmd, CMD_NOP | CMD_DMA);
if (bus_read_1(res, cr0_cmd) != (CMD_NOP | CMD_DMA))
{
#ifdef NCV_DEBUG
printf("ncv: cr0_cmd CMD_NOP|CMD_DMA failed\n");
#endif /* NCV_DEBUG */
return ENODEV;
}
bus_write_1(res, cr0_cmd, CMD_NOP);
if (bus_read_1(res, cr0_cmd) != CMD_NOP)
{
#ifdef NCV_DEBUG
printf("ncv: cr0_cmd CMD_NOP failed\n");
#endif /* NCV_DEBUG */
return ENODEV;
}
/* hardware reset */
ncvhw_reset(res, hw);
ncvhw_init(res, hw);
/* bus reset */
ncvhw_select_register_0(res, hw);
bus_write_1(res, cr0_cmd, CMD_FLUSH);
bus_write_1(res, cr0_cmd, CMD_RSTSCSI);
bus_write_1(res, cr0_cmd, CMD_NOP | CMD_DMA);
DELAY(100 * 1000);
/* check response */
bus_read_1(res, cr0_stat);
stat = bus_read_1(res, cr0_istat);
DELAY(1000);
if (((stat & INTR_SBR) == 0) ||
(bus_read_1(res, cr0_istat) & INTR_SBR))
{
#ifdef NCV_DEBUG
printf("ncv: cr0_istat SCSI BUS RESET failed\n");
#endif /* NCV_DEBUG */
return ENODEV;
}
return 0;
}
static void
ncvhw_reset(struct resource *res, struct ncv_hw *hw)
{
ncvhw_select_register_0(res, hw);
/* dummy cmd twice */
bus_write_1(res, cr0_cmd, CMD_NOP);
bus_write_1(res, cr0_cmd, CMD_NOP);
/* chip reset */
bus_write_1(res, cr0_cmd, CMD_RSTCHIP);
/* again dummy cmd twice */
bus_write_1(res, cr0_cmd, CMD_NOP);
bus_write_1(res, cr0_cmd, CMD_NOP);
}
static void
ncvhw_init(struct resource *res, struct ncv_hw *hw)
{
ncvhw_select_register_0(res, hw);
bus_write_1(res, cr0_clk, hw->hw_clk);
bus_write_1(res, cr0_srtout, SEL_TOUT);
bus_write_1(res, cr0_period, 0);
bus_write_1(res, cr0_offs, 0);
bus_write_1(res, cr0_cfg1, hw->hw_cfg1);
bus_write_1(res, cr0_cfg2, hw->hw_cfg2);
bus_write_1(res, cr0_cfg3, hw->hw_cfg3);
bus_write_1(res, cr0_tchsb, 0);
ncvhw_select_register_1(res, hw);
bus_write_1(res, cr1_fstat, 0x0);
bus_write_1(res, cr1_pflag, 0x0);
bus_write_1(res, cr1_atacmd, ATACMD_ENGAGE);
ncvhw_select_register_0(res, hw);
}
#ifdef NCV_POWER_CONTROL
static int
ncvhw_power(sc, flags)
struct ncv_softc *sc;
u_int flags;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct resource *res = sc->port_res;
if (flags == SCSI_LOW_POWDOWN)
{
device_printf(slp->sl_dev, "power down\n");
ncvhw_select_register_1(res, &sc->sc_hw);
bus_write_1(res, cr1_atacmd, ATACMD_POWDOWN);
}
else
{
switch (sc->sc_rstep)
{
case 0:
device_printf(slp->sl_dev, "resume step O\n");
ncvhw_select_register_1(res, &sc->sc_hw);
bus_write_1(res, cr1_atacmd, ATACMD_ENGAGE);
break;
case 1:
device_printf(slp->sl_dev, "resume step I\n");
ncvhw_reset(res, &sc->sc_hw);
ncvhw_init(res, &sc->sc_hw);
break;
}
}
return 0;
}
#endif /* NCV_POWER_CONTROL */
/**************************************************************
* scsi low interface
**************************************************************/
static void
ncvhw_attention(sc)
struct ncv_softc *sc;
{
bus_write_1(sc->port_res, cr0_cmd, CMD_SETATN);
DELAY(10);
}
static void
ncvhw_bus_reset(sc)
struct ncv_softc *sc;
{
ncvhw_select_register_0(sc->port_res, &sc->sc_hw);
bus_write_1(sc->port_res, cr0_cmd, CMD_FLUSH);
bus_write_1(sc->port_res, cr0_cmd, CMD_RSTSCSI);
bus_write_1(sc->port_res, cr0_cmd, CMD_NOP | CMD_DMA);
}
static int
ncvhw_start_selection(sc, cb)
struct ncv_softc *sc;
struct slccb *cb;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct resource *res = sc->port_res;
struct targ_info *ti = cb->ti;
int len;
u_int flags;
u_int8_t cmd;
sc->sc_tmaxcnt = cb->ccb_tcmax * 1000 * 1000;
sc->sc_compseq = 0;
if (scsi_low_is_msgout_continue(ti, SCSI_LOW_MSG_IDENTIFY) == 0)
{
cmd = CMD_SELATN;
sc->sc_selstop = 0;
flags = SCSI_LOW_MSGOUT_UNIFY | SCSI_LOW_MSGOUT_INIT;
}
else if (scsi_low_is_msgout_continue(ti,
SCSI_LOW_MSG_IDENTIFY | SCSI_LOW_MSG_SIMPLE_QTAG) == 0)
{
cmd = CMD_SELATN3;
sc->sc_selstop = 0;
flags = SCSI_LOW_MSGOUT_UNIFY | SCSI_LOW_MSGOUT_INIT;
}
else
{
cmd = CMD_SELATNS;
sc->sc_selstop = 1;
flags = SCSI_LOW_MSGOUT_INIT;
}
ncvhw_select_register_0(res, &sc->sc_hw);
if ((bus_read_1(res, cr0_stat) & STAT_INT) != 0)
return SCSI_LOW_START_FAIL;
ncv_target_nexus_establish(sc);
len = scsi_low_msgout(slp, ti, flags);
if (sc->sc_selstop == 0)
scsi_low_cmd(slp, ti);
if ((bus_read_1(res, cr0_stat) & STAT_INT) != 0)
return SCSI_LOW_START_FAIL;
bus_write_1(res, cr0_dstid, ti->ti_id);
bus_write_1(res, cr0_cmd, CMD_FLUSH);
ncvhw_fpush(res, ti->ti_msgoutstr, len);
if (sc->sc_selstop == 0)
{
ncvhw_fpush(res,
slp->sl_scp.scp_cmd, slp->sl_scp.scp_cmdlen);
}
bus_write_1(res, cr0_cmd, cmd);
SCSI_LOW_SETUP_PHASE(ti, PH_SELSTART);
return SCSI_LOW_START_OK;
}
static int
ncv_world_start(sc, fdone)
struct ncv_softc *sc;
int fdone;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct resource *res = sc->port_res;
u_int8_t stat;
if ((slp->sl_cfgflags & CFG_NOPARITY) == 0)
sc->sc_hw.hw_cfg1 |= C1_PARENB;
else
sc->sc_hw.hw_cfg1 &= ~C1_PARENB;
ncvhw_reset(res, &sc->sc_hw);
ncvhw_init(res, &sc->sc_hw);
scsi_low_bus_reset(slp);
ncvhw_select_register_0(res, &sc->sc_hw);
bus_read_1(res, cr0_stat);
stat = bus_read_1(res, cr0_istat);
DELAY(1000);
if (((stat & INTR_SBR) == 0) ||
(bus_read_1(res, cr0_istat) & INTR_SBR))
return ENODEV;
return 0;
}
static int
ncv_msg(sc, ti, msg)
struct ncv_softc *sc;
struct targ_info *ti;
u_int msg;
{
struct resource *res = sc->port_res;
struct ncv_targ_info *nti = (void *) ti;
u_int hwcycle, period;
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;
period = ti->ti_maxsynch.period;
hwcycle = (sc->sc_hw.hw_clk == 0) ? 40 : (5 * sc->sc_hw.hw_clk);
hwcycle = 1000 / hwcycle;
if (period < 200 / 4 && period >= 100 / 4)
nti->nti_reg_cfg3 |= sc->sc_hw.hw_cfg3_fscsi;
else
nti->nti_reg_cfg3 &= ~sc->sc_hw.hw_cfg3_fscsi;
period = ((period * 40 / hwcycle) + 5) / 10;
nti->nti_reg_period = period & 0x1f;
nti->nti_reg_offset = ti->ti_maxsynch.offset;
bus_write_1(res, cr0_period, nti->nti_reg_period);
bus_write_1(res, cr0_offs, nti->nti_reg_offset);
bus_write_1(res, cr0_cfg3, nti->nti_reg_cfg3);
return 0;
}
static int
ncv_targ_init(sc, ti, action)
struct ncv_softc *sc;
struct targ_info *ti;
int action;
{
struct ncv_targ_info *nti = (void *) ti;
if (action == SCSI_LOW_INFO_ALLOC || action == SCSI_LOW_INFO_REVOKE)
{
ti->ti_width = SCSI_LOW_BUS_WIDTH_8;
ti->ti_maxsynch.period = sc->sc_hw.hw_mperiod;
ti->ti_maxsynch.offset = sc->sc_hw.hw_moffset;
nti->nti_reg_cfg3 = sc->sc_hw.hw_cfg3;
nti->nti_reg_period = 0;
nti->nti_reg_offset = 0;
}
return 0;
}
/**************************************************************
* General probe attach
**************************************************************/
static int ncv_setup_img(struct ncv_hw *, u_int, int);
static int
ncv_setup_img(hw, dvcfg, hostid)
struct ncv_hw *hw;
u_int dvcfg;
int hostid;
{
if (NCV_CLKFACTOR(dvcfg) > CLK_35M_F)
{
printf("ncv: invalid dvcfg flags\n");
return EINVAL;
}
if (NCV_C5IMG(dvcfg) != 0)
{
hw->hw_cfg5 = NCV_C5IMG(dvcfg);
hw->hw_clk = NCV_CLKFACTOR(dvcfg);
if ((ncv_io_control & NCV_ENABLE_FAST_SCSI) != 0 &&
(NCV_SPECIAL(dvcfg) & NCVHWCFG_MAX10M) != 0)
hw->hw_mperiod = 100 / 4;
if (NCV_SPECIAL(dvcfg) & NCVHWCFG_FIFOBUG)
hw->hw_cfg3_fclk = 0x04;
if (NCV_SPECIAL(dvcfg) & NCVHWCFG_SCSI1)
hw->hw_cfg2 &= ~C2_SCSI2;
if (NCV_SPECIAL(dvcfg) & NCVHWCFG_SLOW)
hw->hw_cfg1 |= C1_SLOW;
}
/* setup configuration image 3 */
if (hw->hw_clk != CLK_40M_F && hw->hw_clk <= CLK_25M_F)
hw->hw_cfg3 &= ~hw->hw_cfg3_fclk;
else
hw->hw_cfg3 |= hw->hw_cfg3_fclk;
/* setup configuration image 1 */
hw->hw_cfg1 = (hw->hw_cfg1 & 0xf0) | hostid;
return 0;
}
int
ncvprobesubr(struct resource *res, u_int dvcfg, int hsid)
{
struct ncv_hw hwtab;
hwtab = ncv_template;
if (ncv_setup_img(&hwtab, dvcfg, hsid))
return 0;
if (ncvhw_check(res, &hwtab) != 0)
return 0;
return 1;
}
void
ncvattachsubr(sc)
struct ncv_softc *sc;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
printf("\n");
sc->sc_hw = ncv_template;
ncv_setup_img(&sc->sc_hw, slp->sl_cfgflags, slp->sl_hostid);
slp->sl_funcs = &ncv_funcs;
slp->sl_flags |= HW_READ_PADDING;
sc->sc_tmaxcnt = SCSI_LOW_MIN_TOUT * 1000 * 1000; /* default */
(void) scsi_low_attach(slp, 0, NCV_NTARGETS, NCV_NLUNS,
sizeof(struct ncv_targ_info), 0);
}
/**************************************************************
* PDMA
**************************************************************/
static __inline void
ncv_setup_and_start_pio(sc, reqlen)
struct ncv_softc *sc;
u_int reqlen;
{
struct resource *res = sc->port_res;
ncvhw_select_register_0(res, &sc->sc_hw);
ncvhw_set_count(res, reqlen);
bus_write_1(res, cr0_cmd, CMD_TRANS | CMD_DMA);
ncvhw_select_register_1(res, &sc->sc_hw);
bus_write_1(res, cr1_fstat, FIFO_EN);
}
static void
ncv_pdma_end(sc, ti)
struct ncv_softc *sc;
struct targ_info *ti;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct resource *res = sc->port_res;
int len;
slp->sl_flags &= ~HW_PDMASTART;
if (slp->sl_Qnexus == NULL)
{
slp->sl_error |= PDMAERR;
goto out;
}
if (ti->ti_phase == PH_DATA)
{
len = ncvhw_get_count(res);
if (slp->sl_scp.scp_direction == SCSI_LOW_WRITE)
len += (bus_read_1(res,
cr0_sffl) & CR0_SFFLR_BMASK);
if ((u_int) len <= (u_int) sc->sc_sdatalen)
{
if ((slp->sl_scp.scp_direction == SCSI_LOW_READ) &&
sc->sc_tdatalen != len)
goto bad;
len = sc->sc_sdatalen - len;
if ((u_int) len > (u_int) slp->sl_scp.scp_datalen)
goto bad;
slp->sl_scp.scp_data += len;
slp->sl_scp.scp_datalen -= len;
}
else
{
bad:
if ((slp->sl_error & PDMAERR) == 0)
{
device_printf(slp->sl_dev,
"strange cnt hw 0x%x soft 0x%x\n", len,
slp->sl_scp.scp_datalen);
}
slp->sl_error |= PDMAERR;
}
scsi_low_data_finish(slp);
}
else
{
device_printf(slp->sl_dev, "data phase miss\n");
slp->sl_error |= PDMAERR;
}
out:
ncvhw_select_register_1(res, &sc->sc_hw);
bus_write_1(res, cr1_fstat, 0);
ncvhw_select_register_0(res, &sc->sc_hw);
}
static void
ncv_pio_read(sc, buf, reqlen)
struct ncv_softc *sc;
u_int8_t *buf;
u_int reqlen;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct resource *res = sc->port_res;
int tout;
register u_int8_t fstat;
ncv_setup_and_start_pio(sc, reqlen);
slp->sl_flags |= HW_PDMASTART;
sc->sc_sdatalen = reqlen;
tout = sc->sc_tmaxcnt;
while (reqlen >= FIFO_F_SZ && tout -- > 0)
{
fstat = bus_read_1(res, cr1_fstat);
if (fstat == (u_int8_t) -1)
goto out;
if (fstat & FIFO_F)
{
#define NCV_FAST32_ACCESS
#ifdef NCV_FAST32_ACCESS
bus_read_multi_4(res, cr1_fdata,
(u_int32_t *) buf, FIFO_F_SZ / 4);
#else /* !NCV_FAST32_ACCESS */
bus_read_multi_2(res, cr1_fdata,
(u_int16_t *) buf, FIFO_F_SZ / 2);
#endif /* !NCV_FAST32_ACCESS */
buf += FIFO_F_SZ;
reqlen -= FIFO_F_SZ;
}
else
{
if (fstat & FIFO_BRK)
break;
DELAY(1);
}
}
while (reqlen > 0 && tout -- > 0)
{
fstat = bus_read_1(res, cr1_fstat);
if ((fstat & FIFO_E) == 0)
{
*buf++ = bus_read_1(res, cr1_fdata);
reqlen --;
}
else
{
if (fstat & FIFO_BRK)
break;
DELAY(1);
}
}
out:
ncvhw_select_register_0(res, &sc->sc_hw);
sc->sc_tdatalen = reqlen;
}
static void
ncv_pio_write(sc, buf, reqlen)
struct ncv_softc *sc;
u_int8_t *buf;
u_int reqlen;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct resource *res = sc->port_res;
int tout;
register u_int8_t fstat;
ncv_setup_and_start_pio(sc, reqlen);
sc->sc_sdatalen = reqlen;
tout = sc->sc_tmaxcnt;
slp->sl_flags |= HW_PDMASTART;
while (reqlen >= FIFO_F_SZ && tout -- > 0)
{
fstat = bus_read_1(res, cr1_fstat);
if (fstat & FIFO_BRK)
goto done;
if ((fstat & FIFO_E) != 0)
{
#ifdef NCV_FAST32_ACCESS
bus_write_multi_4(res, cr1_fdata,
(u_int32_t *) buf, FIFO_F_SZ / 4);
#else /* !NCV_FAST32_ACCESS */
bus_write_multi_2(res, cr1_fdata,
(u_int16_t *) buf, FIFO_F_SZ / 2);
#endif /* !NCV_FAST32_ACCESS */
buf += FIFO_F_SZ;
reqlen -= FIFO_F_SZ;
}
else
{
DELAY(1);
}
}
while (reqlen > 0 && tout -- > 0)
{
fstat = bus_read_1(res, cr1_fstat);
if (fstat & FIFO_BRK)
break;
if ((fstat & FIFO_F) == 0) /* fifo not full */
{
bus_write_1(res, cr1_fdata, *buf++);
reqlen --;
}
else
{
DELAY(1);
}
}
done:
ncvhw_select_register_0(res, &sc->sc_hw);
}
/**************************************************************
* disconnect & reselect (HW low)
**************************************************************/
static int
ncv_reselected(sc)
struct ncv_softc *sc;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct resource *res = sc->port_res;
struct targ_info *ti;
u_int sid;
if ((bus_read_1(res, cr0_sffl) & CR0_SFFLR_BMASK) != 2)
{
device_printf(slp->sl_dev, "illegal fifo bytes\n");
scsi_low_restart(slp, SCSI_LOW_RESTART_HARD, "chip confused");
return EJUSTRETURN;
}
sid = (u_int) bus_read_1(res, cr0_sfifo);
sid &= ~(1 << slp->sl_hostid);
sid = ffs(sid) - 1;
ti = scsi_low_reselected((struct scsi_low_softc *) sc, sid);
if (ti == NULL)
return EJUSTRETURN;
#ifdef NCV_STATICS
ncv_statics.reselect ++;
#endif /* NCV_STATICS */
bus_write_1(res, cr0_dstid, sid);
return 0;
}
static int
ncv_disconnected(sc, ti)
struct ncv_softc *sc;
struct targ_info *ti;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct resource *res = sc->port_res;
bus_write_1(res, cr0_cmd, CMD_FLUSH);
bus_write_1(res, cr0_cmd, CMD_ENSEL);
#ifdef NCV_STATICS
ncv_statics.disconnect ++;
#endif /* NCV_STATICS */
scsi_low_disconnected(slp, ti);
return 1;
}
/**************************************************************
* SEQUENCER
**************************************************************/
static int
ncv_target_nexus_establish(sc)
struct ncv_softc *sc;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct targ_info *ti = slp->sl_Tnexus;
struct ncv_targ_info *nti = (void *) ti;
struct resource *res = sc->port_res;
bus_write_1(res, cr0_period, nti->nti_reg_period);
bus_write_1(res, cr0_offs, nti->nti_reg_offset);
bus_write_1(res, cr0_cfg3, nti->nti_reg_cfg3);
return 0;
}
static int
ncv_lun_nexus_establish(sc)
struct ncv_softc *sc;
{
return 0;
}
static int
ncv_ccb_nexus_establish(sc)
struct ncv_softc *sc;
{
struct scsi_low_softc *slp = &sc->sc_sclow;
struct slccb *cb = slp->sl_Qnexus;
sc->sc_tmaxcnt = cb->ccb_tcmax * 1000 * 1000;
return 0;
}
static int
ncv_catch_intr(sc)
struct ncv_softc *sc;
{
struct resource *res = sc->port_res;
int wc;
register u_int8_t status;
for (wc = 0; wc < NCV_DELAY_MAX / NCV_DELAY_INTERVAL; wc ++)
{
status = bus_read_1(res, cr0_stat);
if ((status & STAT_INT) != 0)
return 0;
DELAY(NCV_DELAY_INTERVAL);
}
return EJUSTRETURN;
}
int
ncvintr(arg)
void *arg;
{
struct ncv_softc *sc = arg;
struct scsi_low_softc *slp = &sc->sc_sclow;
struct resource *res = sc->port_res;
struct targ_info *ti;
struct buf *bp;
u_int derror, flags;
int len;
u_int8_t regv, status, ireason;
again:
if (slp->sl_flags & HW_INACTIVE)
return 0;
/********************************************
* Status
********************************************/
ncvhw_select_register_0(res, &sc->sc_hw);
status = bus_read_1(res, cr0_stat);
if ((status & STAT_INT) == 0 || status == (u_int8_t) -1)
return 0;
ireason = bus_read_1(res, cr0_istat);
if ((ireason & INTR_SBR) != 0)
{
u_int8_t val;
/* avoid power off hangup */
val = bus_read_1(res, cr0_cfg1);
bus_write_1(res, cr0_cfg1, val | C1_SRR);
/* status init */
scsi_low_restart(slp, SCSI_LOW_RESTART_SOFT,
"bus reset (power off?)");
return 1;
}
/********************************************
* Debug section
********************************************/
#ifdef NCV_DEBUG
if (ncv_debug)
{
scsi_low_print(slp, NULL);
device_printf(slp->sl_dev, "st %x ist %x\n\n",
status, ireason);
#ifdef KDB
if (ncv_debug > 1)
kdb_enter(KDB_WHY_CAM, "ncv");
#endif /* KDB */
}
#endif /* NCV_DEBUG */
/********************************************
* Reselect or Disconnect or Nexus check
********************************************/
/* (I) reselect */
if (ireason == INTR_RESELECT)
{
if (ncv_reselected(sc) == EJUSTRETURN)
return 1;
}
/* (II) nexus */
if ((ti = slp->sl_Tnexus) == NULL)
return 0;
derror = 0;
if ((status & (STAT_PE | STAT_GE)) != 0)
{
slp->sl_error |= PARITYERR;
if ((status & PHASE_MASK) == MESSAGE_IN_PHASE)
scsi_low_assert_msg(slp, ti, SCSI_LOW_MSG_PARITY, 0);
else
scsi_low_assert_msg(slp, ti, SCSI_LOW_MSG_ERROR, 1);
derror = SCSI_LOW_DATA_PE;
}
if ((ireason & (INTR_DIS | INTR_ILL)) != 0)
{
if ((ireason & INTR_ILL) == 0)
return ncv_disconnected(sc, ti);
slp->sl_error |= FATALIO;
scsi_low_restart(slp, SCSI_LOW_RESTART_HARD, "illegal cmd");
return 1;
}
/********************************************
* Internal scsi phase
********************************************/
switch (ti->ti_phase)
{
case PH_SELSTART:
scsi_low_arbit_win(slp);
SCSI_LOW_SETUP_PHASE(ti, PH_SELECTED);
if (sc->sc_selstop == 0)
{
/* XXX:
* Here scsi phases expected are
* DATA PHASE:
* MSGIN : target wants to disconnect the host.
* STATUSIN : immediate command completed.
* CMD PHASE : command out failed
* MSGOUT : identify command failed.
*/
if ((status & PHASE_MASK) != MESSAGE_OUT_PHASE)
break;
}
else
{
if ((status & PHASE_MASK) != MESSAGE_OUT_PHASE)
break;
if ((ireason & INTR_FC) != 0)
{
SCSI_LOW_ASSERT_ATN(slp);
}
}
SCSI_LOW_SETUP_PHASE(ti, PH_MSGOUT);
break;
case PH_RESEL:
ncv_target_nexus_establish(sc);
if ((status & PHASE_MASK) != MESSAGE_IN_PHASE)
{
device_printf(slp->sl_dev,
"unexpected phase after reselect\n");
slp->sl_error |= FATALIO;
scsi_low_assert_msg(slp, ti, SCSI_LOW_MSG_ABORT, 1);
return 1;
}
break;
default:
if ((slp->sl_flags & HW_PDMASTART) != 0)
{
ncv_pdma_end(sc, ti);
}
break;
}
/********************************************
* Scsi phase sequencer
********************************************/
switch (status & PHASE_MASK)
{
case DATA_OUT_PHASE: /* data out */
SCSI_LOW_SETUP_PHASE(ti, PH_DATA);
if (scsi_low_data(slp, ti, &bp, SCSI_LOW_WRITE) != 0)
{
scsi_low_attention(slp);
}
if (slp->sl_scp.scp_datalen <= 0)
{
if ((ireason & INTR_BS) == 0)
break;
if ((slp->sl_error & PDMAERR) == 0)
device_printf(slp->sl_dev, "data underrun\n");
slp->sl_error |= PDMAERR;
if ((slp->sl_flags & HW_WRITE_PADDING) != 0)
{
u_int8_t padding[NCV_PADDING_SIZE];
bzero(padding, sizeof(padding));
ncv_pio_write(sc, padding, sizeof(padding));
}
else
{
device_printf(slp->sl_dev,
"write padding required\n");
}
}
else
{
len = slp->sl_scp.scp_datalen;
if ((ncv_io_control & NCV_WRITE_INTERRUPTS_DRIVEN) != 0)
{
if (len > ncv_data_write_bytes)
len = ncv_data_write_bytes;
}
ncv_pio_write(sc, slp->sl_scp.scp_data, len);
}
break;
case DATA_IN_PHASE: /* data in */
SCSI_LOW_SETUP_PHASE(ti, PH_DATA);
if (scsi_low_data(slp, ti, &bp, SCSI_LOW_READ) != 0)
{
scsi_low_attention(slp);
}
if (slp->sl_scp.scp_datalen <= 0)
{
if ((ireason & INTR_BS) == 0)
break;
if ((slp->sl_error & PDMAERR) == 0)
device_printf(slp->sl_dev, "data overrun\n");
slp->sl_error |= PDMAERR;
if ((slp->sl_flags & HW_READ_PADDING) != 0)
{
u_int8_t padding[NCV_PADDING_SIZE];
ncv_pio_read(sc, padding, sizeof(padding));
}
else
{
device_printf(slp->sl_dev,
"read padding required\n");
break;
}
}
else
{
len = slp->sl_scp.scp_datalen;
if ((ncv_io_control & NCV_READ_INTERRUPTS_DRIVEN) != 0)
{
if (len > ncv_data_read_bytes)
len = ncv_data_read_bytes;
}
ncv_pio_read(sc, slp->sl_scp.scp_data, len);
}
break;
case COMMAND_PHASE: /* cmd out */
SCSI_LOW_SETUP_PHASE(ti, PH_CMD);
if (scsi_low_cmd(slp, ti) != 0)
{
scsi_low_attention(slp);
}
bus_write_1(res, cr0_cmd, CMD_FLUSH);
ncvhw_fpush(res,
slp->sl_scp.scp_cmd, slp->sl_scp.scp_cmdlen);
bus_write_1(res, cr0_cmd, CMD_TRANS);
break;
case STATUS_PHASE: /* status in */
SCSI_LOW_SETUP_PHASE(ti, PH_STAT);
bus_write_1(res, cr0_cmd, CMD_FLUSH);
bus_write_1(res, cr0_cmd, CMD_ICCS);
sc->sc_compseq = 1;
break;
default:
break;
case MESSAGE_OUT_PHASE: /* msg out */
SCSI_LOW_SETUP_PHASE(ti, PH_MSGOUT);
bus_write_1(res, cr0_cmd, CMD_FLUSH);
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)
{
scsi_low_attention(slp);
}
ncvhw_fpush(res, ti->ti_msgoutstr, len);
bus_write_1(res, cr0_cmd, CMD_TRANS);
SCSI_LOW_DEASSERT_ATN(slp);
break;
case MESSAGE_IN_PHASE: /* msg in */
SCSI_LOW_SETUP_PHASE(ti, PH_MSGIN);
len = bus_read_1(res, cr0_sffl) & CR0_SFFLR_BMASK;
if (sc->sc_compseq != 0)
{
sc->sc_compseq = 0;
if ((ireason & INTR_FC) && len == 2)
{
regv = bus_read_1(res, cr0_sfifo);
scsi_low_statusin(slp, ti, regv | derror);
len --;
}
else
{
slp->sl_error |= FATALIO;
scsi_low_assert_msg(slp, ti,
SCSI_LOW_MSG_ABORT, 1);
bus_write_1(res, cr0_cmd, CMD_MSGOK);
break;
}
}
else if (ireason & INTR_BS)
{
bus_write_1(res, cr0_cmd, CMD_FLUSH);
bus_write_1(res, cr0_cmd, CMD_TRANS);
if ((ncv_io_control & NCV_FAST_INTERRUPTS) != 0)
{
if (ncv_catch_intr(sc) == 0)
goto again;
}
break;
}
if ((ireason & INTR_FC) && len == 1)
{
regv = bus_read_1(res, cr0_sfifo);
if (scsi_low_msgin(slp, ti, regv | derror) == 0)
{
if (scsi_low_is_msgout_continue(ti, 0) != 0)
{
scsi_low_attention(slp);
}
}
bus_write_1(res, cr0_cmd, CMD_MSGOK);
if ((ncv_io_control & NCV_FAST_INTERRUPTS) != 0)
{
/* XXX:
* clear a pending interrupt and sync with
* a next interrupt!
*/
ncv_catch_intr(sc);
}
}
else
{
slp->sl_error |= FATALIO;
scsi_low_assert_msg(slp, ti, SCSI_LOW_MSG_ABORT, 1);
bus_write_1(res, cr0_cmd, CMD_MSGOK);
}
break;
}
return 1;
}