freebsd-skq/sys/dev/ncv/ncr53c500.c
pfg 1537078d8f sys/dev: further adoption of SPDX licensing ID tags.
Mainly focus on files that use BSD 2-Clause license, however the tool I
was using misidentified many licenses so this was mostly a manual - error
prone - task.

The Software Package Data Exchange (SPDX) group provides a specification
to make it easier for automated tools to detect and summarize well known
opensource licenses. We are gradually adopting the specification, noting
that the tags are considered only advisory and do not, in any way,
superceed or replace the license texts.
2017-11-27 14:52:40 +00:00

1241 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)
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* [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;
}