NetBSD/sys/arch/next68k/stand/boot/scsi.c

443 lines
11 KiB
C

/* $NetBSD: scsi.c,v 1.2 1999/03/26 06:54:40 dbj 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/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 caddr_t sr);
int scsi_wait_for_intr(void);
int scsiicmd(char target, char lun,
u_char *cbuf, int clen, char *addr, int len);
#ifdef SCSI_DEBUG
#define DPRINTF(x) printf x;
#else
#define DPRINTF(x)
#endif
void
scsi_init(void)
{
volatile caddr_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[ESP_CMD] = ESPCMD_RSTCHIP;
sr[ESP_CMD] = ESPCMD_NOP;
DELAY(500);
/* now reset the SCSI bus */
sr[ESP_CMD] = ESPCMD_RSTSCSI;
DELAY(18000000); /* XXX should be about 2-3 seconds at least */
/* then reset the SCSI chip again and initialize it properly */
sr[ESP_CMD] = ESPCMD_RSTCHIP;
sr[ESP_CMD] = ESPCMD_NOP;
DELAY(500);
sr[ESP_CFG1] = ESPCFG1_SLOW | ESPCFG1_BUSID;
sr[ESP_CFG2] = 0;
sr[ESP_CCF] = 4; /* S5RCLKCONV_FACTOR(20); */
sr[ESP_TIMEOUT] = 152; /* S5RSELECT_TIMEOUT(20,250); */
sr[ESP_SYNCOFF] = 0;
sr[ESP_SYNCTP] = 5;
/*
sc->sc_intrstatus = sr->s5r_intrstatus;
sc->sc_intrstatus = sr->s5r_intrstatus;
*/
sr[ESP_CFG1] = ESPCFG1_PARENB | ESPCFG1_BUSID;
sc->sc_state = SCSI_IDLE;
}
void
scsierror(char *error)
{
printf("scsierror: %s.\n", error);
}
short
scsi_getbyte(volatile caddr_t sr)
{
if ((sr[ESP_FFLAG] & ESPFIFO_FF) == 0)
{
printf("getbyte: no data!\n");
return -1;
}
return sr[ESP_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);
volatile int *intrmask = MON(volatile int *,MG_intrmask);
#endif
int count;
for(count = 0; count < SCSI_TIMEOUT; count++) {
DPRINTF((" *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 caddr_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[ESP_CMD] = ESPCMD_FLUSH;
DELAY(10);
sr[ESP_SELID] = target;
sr[ESP_FIFO] = MSG_IDENTIFY(lun, 0);
for (i=0; i<clen; i++)
sr[ESP_FIFO] = cbuf[i];
sr[ESP_CMD] = ESPCMD_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"));
} else {
/* scsi processing */
sc->sc_status = sr[ESP_STAT];
sc->sc_seqstep = sr[ESP_STEP];
sc->sc_intrstatus = sr[ESP_INTR];
DPRINTF(("scsiicmd: regs[intr=%x, stat=%x, step=%x]\n",
sc->sc_intrstatus, sc->sc_status, sc->sc_seqstep));
}
if (sc->sc_intrstatus & ESPINTR_SBR) {
scsierror("scsi bus reset");
return EIO;
}
if ((sc->sc_status & ESPSTAT_GE)
|| (sc->sc_intrstatus & ESPINTR_ILL)) {
scsierror("software error");
return EIO;
}
if (sc->sc_status & ESPSTAT_PE)
{
scsierror("parity error");
return EIO;
}
switch(sc->sc_state)
{
case SCSI_SELECTING:
if (sc->sc_intrstatus & ESPINTR_DIS)
{
sc->sc_state = SCSI_IDLE;
return EUNIT; /* device not present */
}
#define ESPINTR_DONE (ESPINTR_BS | ESPINTR_FC)
if ((sc->sc_intrstatus & ESPINTR_DONE) != ESPINTR_DONE)
{
scsierror("selection failed");
return EIO;
}
sc->sc_state = SCSI_HASBUS;
break;
case SCSI_HASBUS:
if (sc->sc_intrstatus & ESPINTR_DIS)
{
scsierror("target disconnected");
return EIO;
}
break;
case SCSI_DMA:
if (sc->sc_intrstatus & ESPINTR_DIS)
{
scsierror("target disconnected");
return EIO;
}
if (dma_done() != 0)
return EIO;
continue;
case SCSI_CLEANUP:
if (sc->sc_intrstatus & ESPINTR_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 & ESPSTAT_PHASE)
{
case DATA_IN_PHASE:
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[ESP_CMD] = ESPCMD_ICCS;
sc->sc_result = scsi_getbyte(sr);
DPRINTF(("status is 0x%x.\n", sc->sc_result));
break;
case MSG_IN_PHASE:
if (scsi_msgin() != 0)
return EIO;
break;
default:
DPRINTF(("phase not implemented: 0x%x.\n",
sc->sc_status & ESPSTAT_PHASE));
scsierror("bad phase");
return EIO;
}
}
sc->sc_state = SCSI_IDLE;
return -sc->sc_result;
}
int
scsi_msgin(void)
{
volatile caddr_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 & ESPINTR_FC) == 0)
{
printf("not function complete.\n");
return -1;
}
sc->sc_state = SCSI_CLEANUP;
sr[ESP_CMD] = ESPCMD_MSGOK;
return 0;
}
int
dma_start(char *addr, int len)
{
volatile caddr_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[ESP_TCL] = 0;
sr[ESP_TCM] = 1;
sr[ESP_CMD] = ESPCMD_NOP;
sr[ESP_CMD] = ESPCMD_DMA | ESPCMD_TRPAD;
return 0;
#else
scsierror("unrequested dma");
return -1;
#endif
}
DPRINTF(("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[ESP_TCL] = len & 0xff;
sr[ESP_TCM] = len >> 8;
sr[ESP_CMD] = ESPCMD_DMA | ESPCMD_NOP;
sr[ESP_CMD] = ESPCMD_DMA | ESPCMD_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_initbuf = 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 caddr_t sr;
struct dma_dev *dma;
int count, state;
sr = P_SCSI;
dma = (struct dma_dev *)P_SCSI_CSR;
state = dma->dd_csr & (DMACSR_BUSEXC | DMACSR_COMPLETE
| DMACSR_SUPDATE | DMACSR_ENABLE);
count = sr[ESP_TCM]<<8 | sr[ESP_TCL];
DPRINTF(("dma state = 0x%x, remain = %d.\n", state, count));
if (state & DMACSR_ENABLE)
{
DPRINTF(("dma still enabled, flushing DCTL.\n"));
sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB | ESPDCTL_DMAMOD
| ESPDCTL_DMARD | ESPDCTL_FLUSH;
/* DELAY(5); */
sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB | ESPDCTL_DMAMOD
| ESPDCTL_DMARD;
/* DELAY(5); */
return 0;
}
sr[ESP_DCTL] = ESPDCTL_20MHZ | ESPDCTL_INTENB;
count = sr[ESP_TCM]<<8 | sr[ESP_TCL];
dma->dd_csr = DMACSR_RESET;
DPRINTF(("dma done. remain = %d, state = 0x%x.\n", count, state));
if (count != 0)
{
printf("WARNING: unexpected %d characters remain in dma\n",count);
scsierror("dma transfer incomplete");
#if 0
return -1;
#endif
}
if (state & DMACSR_COMPLETE)
{
bcopy(dma_buffer, sc->dma_addr, sc->dma_len);
sc->sc_state = SCSI_HASBUS;
return 0;
}
if (state & DMACSR_BUSEXC)
{
scsierror("dma failed");
return -1;
}
scsierror("dma not completed\n");
return -1;
}