/* $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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }