473 lines
12 KiB
C
473 lines
12 KiB
C
/* $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 <sys/param.h>
|
|
#include <next68k/dev/espreg.h>
|
|
#include <dev/ic/ncr53c9xreg.h>
|
|
#include <dev/scsipi/scsi_message.h>
|
|
#if 0
|
|
#include <next/next/prominfo.h>
|
|
#else
|
|
#include <next68k/next68k/nextrom.h>
|
|
#endif
|
|
#include "scsireg.h"
|
|
#include "dmareg.h"
|
|
#include "scsivar.h"
|
|
|
|
#include <lib/libsa/stand.h>
|
|
|
|
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; i<clen; i++)
|
|
sr[NCR_FIFO] = cbuf[i];
|
|
sr[NCR_CMD] = NCRCMD_SELATN;
|
|
sc->sc_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;
|
|
}
|