/* $NetBSD: scsi.c,v 1.10 2008/03/30 16:28:08 he Exp $ */ /* * Copyright (c) 1994, 1997 Rolf Grossmann * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Rolf Grossmann. * 4. 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 #include #include #include #if 0 #include #else #include #endif #include "scsireg.h" #include "dmareg.h" #include "scsivar.h" #include struct scsi_softc scsi_softc, *sc = &scsi_softc; char the_dma_buffer[MAX_DMASIZE+DMA_ENDALIGNMENT], *dma_buffer; int scsi_msgin(void); int dma_start(char *addr, int len); int dma_done(void); void scsi_init(void); void scsierror(char *error); short scsi_getbyte(volatile uint8_t *sr); int scsi_wait_for_intr(void); int scsiicmd(char target, char lun, u_char *cbuf, int clen, char *addr, int *len); #define NDPRINTF(x) #define PRINTF(x) /* printf x; */ #ifdef xSCSI_DEBUG #define DPRINTF(x) printf x; #else #define DPRINTF(x) #endif void scsi_init(void) { volatile uint8_t *sr; struct dma_dev *dma; sr = P_SCSI; dma = (struct dma_dev *)P_SCSI_CSR; dma_buffer = DMA_ALIGN(char *, the_dma_buffer); P_FLOPPY[FLP_CTRL] &= ~FLC_82077_SEL; /* select SCSI chip */ /* first reset DMA */ dma->dd_csr = DMACSR_RESET; DELAY(200); sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB | ESPDCTL_RESET; DELAY(10); sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB; DELAY(10); /* then reset the SCSI chip */ sr[NCR_CMD] = NCRCMD_RSTCHIP; sr[NCR_CMD] = NCRCMD_NOP; DELAY(500); /* now reset the SCSI bus */ sr[NCR_CMD] = NCRCMD_RSTSCSI; DELAY(4000000); /* XXX should be about 2-3 seconds at least */ /* then reset the SCSI chip again and initialize it properly */ sr[NCR_CMD] = NCRCMD_RSTCHIP; sr[NCR_CMD] = NCRCMD_NOP; DELAY(500); sr[NCR_CFG1] = NCRCFG1_SLOW | NCRCFG1_BUSID; sr[NCR_CFG2] = 0; sr[NCR_CCF] = 4; /* S5RCLKCONV_FACTOR(20); */ sr[NCR_TIMEOUT] = 152; /* S5RSELECT_TIMEOUT(20,250); */ sr[NCR_SYNCOFF] = 0; sr[NCR_SYNCTP] = 5; /* sc->sc_intrstatus = sr->s5r_intrstatus; sc->sc_intrstatus = sr->s5r_intrstatus; */ sr[NCR_CFG1] = NCRCFG1_PARENB | NCRCFG1_BUSID; sc->sc_state = SCSI_IDLE; } void scsierror(char *error) { printf("scsierror: %s.\n", error); } short scsi_getbyte(volatile uint8_t *sr) { if ((sr[NCR_FFLAG] & NCRFIFO_FF) == 0) { printf("getbyte: no data!\n"); return -1; } return sr[NCR_FIFO]; } int scsi_wait_for_intr(void) { #if 0 extern struct prominfo *pi; volitle int = pi->pi_intrstat; /* ### use constant? */ #else extern char *mg; #define MON(type, off) (*(type *)((u_int) (mg) + off)) volatile int *intrstat = MON(volatile int *,MG_intrstat); #ifdef SCSI_DEBUG /* volatile int *intrmask = MON(volatile int *,MG_intrmask); */ #endif #endif int count; for(count = 0; count < SCSI_TIMEOUT; count++) { NDPRINTF((" *intrstat = 0x%x\t*intrmask = 0x%x\n",*intrstat,*intrmask)); if (*intrstat & SCSI_INTR) return 0; } printf("scsiicmd: timed out.\n"); return -1; } int scsiicmd(char target, char lun, u_char *cbuf, int clen, char *addr, int *len) { volatile uint8_t *sr; int i; DPRINTF(("scsiicmd: [%x, %d] -> %d (%lx, %d)\n",*cbuf, clen, target, (long)addr, *len)); sr = P_SCSI; if (sc->sc_state != SCSI_IDLE) { scsierror("scsiiscmd: bad state"); return EIO; } sc->sc_result = 0; /* select target */ sr[NCR_CMD] = NCRCMD_FLUSH; DELAY(10); sr[NCR_SELID] = target; sr[NCR_FIFO] = MSG_IDENTIFY(lun, 0); for (i=0; isc_state = SCSI_SELECTING; while(sc->sc_state != SCSI_DONE) { if (scsi_wait_for_intr()) /* maybe we'd better use real intrs ? */ return EIO; if (sc->sc_state == SCSI_DMA) { /* registers are not valid on DMA intr */ sc->sc_status = sc->sc_seqstep = sc->sc_intrstatus = 0; DPRINTF(("scsiicmd: DMA intr\n")); sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB | ESPDCTL_DMARD; } /* scsi processing */ sc->sc_status = sr[NCR_STAT]; sc->sc_seqstep = sr[NCR_STEP]; sc->sc_intrstatus = sr[NCR_INTR]; redo: DPRINTF(("scsiicmd: regs[intr=%x, stat=%x, step=%x]\n", sc->sc_intrstatus, sc->sc_status, sc->sc_seqstep)); if (sc->sc_intrstatus & NCRINTR_SBR) { scsierror("scsi bus reset"); return EIO; } if ((sc->sc_status & NCRSTAT_GE) || (sc->sc_intrstatus & NCRINTR_ILL)) { scsierror("software error"); return EIO; } if (sc->sc_status & NCRSTAT_PE) { scsierror("parity error"); return EIO; } switch(sc->sc_state) { case SCSI_SELECTING: if (sc->sc_intrstatus & NCRINTR_DIS) { sc->sc_state = SCSI_IDLE; return EUNIT; /* device not present */ } #define NCRINTR_DONE (NCRINTR_BS | NCRINTR_FC) if ((sc->sc_intrstatus & NCRINTR_DONE) != NCRINTR_DONE) { scsierror("selection failed"); return EIO; } sc->sc_state = SCSI_HASBUS; break; case SCSI_HASBUS: if (sc->sc_intrstatus & NCRINTR_DIS) { scsierror("target disconnected"); return EIO; } break; case SCSI_DMA: if (sc->sc_intrstatus & NCRINTR_DIS) { scsierror("target disconnected"); return EIO; } *len = dma_done(); if (*len < 0) { *len = 0; return EIO; } /* continue; */ sc->sc_status = sr[NCR_STAT]; goto redo; break; case SCSI_CLEANUP: if (sc->sc_intrstatus & NCRINTR_DIS) { sc->sc_state = SCSI_DONE; continue; } DPRINTF(("hmm ... no disconnect on cleanup?\n")); sc->sc_state = SCSI_DONE; /* maybe ... */ break; } /* transfer information now */ switch(sc->sc_status & NCRSTAT_PHASE) { case DATA_IN_PHASE: sr[NCR_CMD] = NCRCMD_FLUSH; if (dma_start(addr, *len) != 0) return EIO; break; case DATA_OUT_PHASE: scsierror("data out phase not implemented"); return EIO; case STATUS_PHASE: DPRINTF(("status phase: ")); sr[NCR_CMD] = NCRCMD_ICCS; sc->sc_result = scsi_getbyte(sr); DPRINTF(("status is 0x%x.\n", sc->sc_result)); break; case MSG_IN_PHASE: if ((sc->sc_intrstatus & NCRINTR_BS) != 0) { sr[NCR_CMD] = NCRCMD_FLUSH; sr[NCR_CMD] = NCRCMD_TRANS; } else if (scsi_msgin() != 0) return EIO; break; default: DPRINTF(("phase not implemented: 0x%x.\n", sc->sc_status & NCRSTAT_PHASE)); scsierror("bad phase"); return EIO; } } sc->sc_state = SCSI_IDLE; return -sc->sc_result; } int scsi_msgin(void) { volatile uint8_t *sr; u_char msg; sr = P_SCSI; msg = scsi_getbyte(sr); if (msg) { printf("unexpected msg: 0x%x.\n",msg); return -1; } if ((sc->sc_intrstatus & NCRINTR_FC) == 0) { printf("not function complete.\n"); return -1; } sc->sc_state = SCSI_CLEANUP; sr[NCR_CMD] = NCRCMD_MSGOK; return 0; } int dma_start(char *addr, int len) { volatile uint8_t *sr; struct dma_dev *dma; sr = P_SCSI; dma = (struct dma_dev *)P_SCSI_CSR; if (len > MAX_DMASIZE) { scsierror("DMA too long"); return -1; } if (addr == NULL || len == 0) { #if 0 /* I'd take that as an error in my code */ DPRINTF(("hmm ... no DMA requested.\n")); sr[NCR_TCL] = 0; sr[NCR_TCM] = 1; sr[NCR_CMD] = NCRCMD_NOP; sr[NCR_CMD] = NCRCMD_DMA | NCRCMD_TRPAD; return 0; #else scsierror("unrequested DMA"); return -1; #endif } PRINTF(("DMA start: %lx, %d byte.\n", (long)addr, len)); DPRINTF(("dma_bufffer: start: 0x%lx end: 0x%lx \n", (long)dma_buffer,(long)DMA_ENDALIGN(char *, dma_buffer+len))); sc->dma_addr = addr; sc->dma_len = len; sr[NCR_TCL] = len & 0xff; sr[NCR_TCM] = len >> 8; sr[NCR_CMD] = NCRCMD_DMA | NCRCMD_NOP; sr[NCR_CMD] = NCRCMD_DMA | NCRCMD_TRANS; #if 0 dma->dd_csr = DMACSR_READ | DMACSR_RESET; dma->dd_next_initbuf = dma_buffer; dma->dd_limit = DMA_ENDALIGN(char *, dma_buffer+len); dma->dd_csr = DMACSR_READ | DMACSR_SETENABLE; #else dma->dd_csr = 0; dma->dd_csr = DMACSR_INITBUF | DMACSR_READ | DMACSR_RESET; dma->dd_next = dma_buffer; dma->dd_limit = DMA_ENDALIGN(char *, dma_buffer+len); dma->dd_csr = DMACSR_READ | DMACSR_SETENABLE; #endif sr[ESP_DCTL] = ESPDCTL_20MHZ|ESPDCTL_INTENB|ESPDCTL_DMAMOD|ESPDCTL_DMARD; sc->sc_state = SCSI_DMA; return 0; } int dma_done(void) { volatile uint8_t *sr; struct dma_dev *dma; int resid, state; int flushcount = 0; sr = P_SCSI; dma = (struct dma_dev *)P_SCSI_CSR; state = dma->dd_csr & (DMACSR_BUSEXC | DMACSR_COMPLETE | DMACSR_SUPDATE | DMACSR_ENABLE); sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB | ESPDCTL_DMARD; resid = sr[NCR_TCM]<<8 | sr[NCR_TCL]; DPRINTF(("DMA state = 0x%x, remain = %d.\n", state, resid)); if (!(sr[NCR_FFLAG] & NCRFIFO_FF)) { sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB | ESPDCTL_DMAMOD | ESPDCTL_DMARD; while (!(state & DMACSR_COMPLETE) && (state & DMACSR_ENABLE) && flushcount < 16) { DPRINTF(("DMA still enabled, flushing DCTL.\n")); sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB | ESPDCTL_DMAMOD | ESPDCTL_DMARD | ESPDCTL_FLUSH; sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB | ESPDCTL_DMAMOD | ESPDCTL_DMARD; flushcount++; state = dma->dd_csr & (DMACSR_BUSEXC | DMACSR_COMPLETE | DMACSR_SUPDATE | DMACSR_ENABLE); } } sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB; resid = (sr[NCR_TCM]<<8) + sr[NCR_TCL]; dma->dd_csr = DMACSR_CLRCOMPLETE | DMACSR_RESET; DPRINTF(("DMA done. remain = %d, state = 0x%x, fifo = 0x%x.\n", resid, state, sr[NCR_FFLAG] & NCRFIFO_FF)); if (resid != 0) { #if 1 printf("WARNING: unexpected %d characters remain in DMA\n",resid); scsierror("DMA transfer incomplete"); return -1; #endif } if (state & DMACSR_BUSEXC) { #if 0 scsierror("DMA failed"); return -1; #endif } sc->dma_len -= resid; if (sc->dma_len < 0) sc->dma_len = 0; memcpy(sc->dma_addr, dma_buffer, sc->dma_len); sc->sc_state = SCSI_HASBUS; DPRINTF(("DMA done. got %d.\n", sc->dma_len)); return sc->dma_len; /* scsierror("DMA not completed\n"); */ return 0; }