/* $NetBSD: mac68k5380.c,v 1.15 1995/11/01 04:59:03 briggs Exp $ */ /* * Copyright (c) 1995 Allen Briggs * 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 Allen Briggs * 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. * * Derived from atari5380.c for the mac68k port of NetBSD. * */ #include #include #include #include #include #include #include #include #include #include /* * Include the driver definitions */ #include #include #include "../mac68k/via.h" /* * Set the various driver options */ #define NREQ 18 /* Size of issue queue */ #define AUTO_SENSE 1 /* Automatically issue a request-sense */ #define DRNAME ncrscsi /* used in various prints */ #undef DBG_SEL /* Show the selection process */ #undef DBG_REQ /* Show enqueued/ready requests */ #undef DBG_NOWRITE /* Do not allow writes to the targets */ #undef DBG_PIO /* Show the polled-I/O process */ #undef DBG_INF /* Show information transfer process */ #define DBG_NOSTATIC /* No static functions, all in DDB trace*/ #define DBG_PID 15 /* Keep track of driver */ #undef REAL_DMA /* Use DMA if sensible */ #define fair_to_keep_dma() 1 #define claimed_dma() 1 #define reconsider_dma() #define USE_PDMA 1 /* Use special pdma-transfer function */ #define MIN_PHYS 0x2000 /* pdma space w/ /DSACK is only 0x2000 */ #define ENABLE_NCR5380(sc) cur_softc = sc; /* * softc of currently active controller (well, we only have one for now). */ static struct ncr_softc *cur_softc; struct scsi_5380 { volatile u_char scsi_5380[8*16]; /* 8 regs, 1 every 16th byte. */ }; extern vm_offset_t SCSIBase; static volatile u_char *ncr = (volatile u_char *) 0x10000; static volatile u_char *ncr_5380_with_drq = (volatile u_char *) 0x6000; static volatile u_char *ncr_5380_without_drq = (volatile u_char *) 0x12000; static volatile u_char *scsi_enable = NULL; #define SCSI_5380 ((struct scsi_5380 *) ncr) #define GET_5380_REG(rnum) SCSI_5380->scsi_5380[((rnum)<<4)] #define SET_5380_REG(rnum,val) (SCSI_5380->scsi_5380[((rnum)<<4)] = (val)) void ncr5380_irq_intr(void *); void ncr5380_drq_intr(void *); static __inline__ void scsi_clr_ipend() { int tmp; tmp = GET_5380_REG(NCR5380_IRCV); } extern __inline__ void scsi_ienable() { int s; s = splhigh(); *scsi_enable = 0x80 | (V2IF_SCSIIRQ | V2IF_SCSIDRQ); splx(s); } extern __inline__ void scsi_idisable() { int s; s = splhigh(); *scsi_enable = V2IF_SCSIIRQ | V2IF_SCSIDRQ; splx(s); } static void scsi_mach_init(sc) struct ncr_softc *sc; { static int initted = 0; if (initted++) panic("scsi_mach_init called again.\n"); ncr = (volatile u_char *) (SCSIBase + (u_long) ncr); ncr_5380_with_drq = (volatile u_char *) (SCSIBase + (u_int) ncr_5380_with_drq); ncr_5380_without_drq = (volatile u_char *) (SCSIBase + (u_int) ncr_5380_without_drq); if (VIA2 == VIA2OFF) scsi_enable = Via1Base + VIA2 * 0x2000 + vIER; else scsi_enable = Via1Base + VIA2 * 0x2000 + rIER; mac68k_register_scsi_irq(ncr5380_irq_intr, sc); mac68k_register_scsi_drq(ncr5380_drq_intr, sc); } static int machine_match(pdp, cdp, auxp, cd) struct device *pdp; struct cfdata *cdp; void *auxp; struct cfdriver *cd; { if (matchbyname(pdp, cdp, auxp) == 0) return 0; if (!mac68k_machine.scsi80) return 0; if (cdp->cf_unit != 0) return 0; return 1; } #if USE_PDMA int pdma_5380_dir = 0; u_char *pending_5380_data; u_long pending_5380_count; #define NCR5380_PDMA_DEBUG 1 /* Maybe we try with this off eventually. */ #if NCR5380_PDMA_DEBUG int pdma_5380_sends = 0; int pdma_5380_bytes = 0; char *pdma_5380_state="", *pdma_5380_prev_state=""; #define DBG_SET(x) {pdma_5380_prev_state=pdma_5380_state; pdma_5380_state=(x);} void pdma_stat() { printf("PDMA SCSI: %d xfers completed for %d bytes.\n", pdma_5380_sends, pdma_5380_bytes); printf("pdma_5380_dir = %d\t", pdma_5380_dir); printf("datap = 0x%x, remainder = %d.\n", pending_5380_data, pending_5380_count); printf("state: %s\t", pdma_5380_state); printf("last state: %s\n", pdma_5380_prev_state); scsi_show(); } #endif void pdma_cleanup(void) { SC_REQ *reqp = connected; int bytes, s; s = splbio(); pdma_5380_dir = 0; #if NCR5380_PDMA_DEBUG DBG_SET("in pdma_cleanup().") pdma_5380_sends++; pdma_5380_bytes+=(reqp->xdata_len - pending_5380_count); #endif /* * Update pointers. */ reqp->xdata_ptr += reqp->xdata_len - pending_5380_count; reqp->xdata_len = pending_5380_count; /* * Reset DMA mode. */ SET_5380_REG(NCR5380_MODE, GET_5380_REG(NCR5380_MODE) & ~SC_M_DMA); /* * Clear any pending interrupts. */ scsi_clr_ipend(); /* * Tell interrupt functions that DMA has ended. */ reqp->dr_flag &= ~DRIVER_IN_DMA; SET_5380_REG(NCR5380_MODE, IMODE_BASE); SET_5380_REG(NCR5380_ICOM, 0); splx(s); /* * Back for more punishment. */ #if NCR5380_PDMA_DEBUG pdma_5380_state = "pdma_cleanup() -- going back to run_main()."; #endif run_main(cur_softc); #if NCR5380_PDMA_DEBUG pdma_5380_state = "pdma_cleanup() -- back from run_main()."; #endif } #endif static __inline__ int pdma_ready() { #if USE_PDMA SC_REQ *reqp = connected; int dmstat, idstat; extern u_char ncr5380_no_parchk; if (pdma_5380_dir) { #if NCR5380_PDMA_DEBUG DBG_SET("got irq interrupt in xfer.") #endif /* * If Mr. IRQ isn't set one might wonder how we got * here. It does happen, though. */ dmstat = GET_5380_REG(NCR5380_DMSTAT); if (!(dmstat & SC_IRQ_SET)) { #if NCR5380_PDMA_DEBUG DBG_SET("irq not set.") #endif return 0; } /* * For a phase mis-match, ATN is a "don't care," IRQ is 1 and * all other bits in the Bus & Status Register are 0. Also, * the current SCSI Bus Status Register has a 1 for BSY and * REQ. Since we're just checking that this interrupt isn't a * reselection or a reset, we just check for either. */ idstat = GET_5380_REG(NCR5380_IDSTAT); if ( ((dmstat & (0xff & ~SC_ATN_STAT)) == SC_IRQ_SET) && ((idstat & (SC_S_BSY|SC_S_REQ)) == (SC_S_BSY | SC_S_REQ)) ) { #if NCR5380_PDMA_DEBUG DBG_SET("BSY|REQ.") #endif pdma_cleanup(); return 1; } else if (PH_IN(reqp->phase) && (dmstat & SC_PAR_ERR)) { if (!(ncr5380_no_parchk & (1 << reqp->targ_id))) /* XXX: Should be parity error ???? */ reqp->xs->error = XS_DRIVER_STUFFUP; #if NCR5380_PDMA_DEBUG DBG_SET("PARITY.") #endif /* XXX: is this the right reaction? */ pdma_cleanup(); return 1; } else if ( !(idstat & SC_S_REQ) || (((idstat>>2) & 7) != reqp->phase)) { #ifdef DIAGNOSTIC /* XXX: is this the right reaction? Can this happen? */ scsi_show(); printf("Unexpected phase change.\n"); #endif reqp->xs->error = XS_DRIVER_STUFFUP; pdma_cleanup(); return 1; } else { scsi_show(); panic("Spurious interrupt during PDMA xfer.\n"); } } #endif return 0; } void ncr5380_irq_intr(p) void *p; { struct ncr_softc *sc = p; #if USE_PDMA if (pdma_ready()) { return; } #endif if (GET_5380_REG(NCR5380_DMSTAT) & SC_IRQ_SET) { scsi_idisable(); ncr_ctrl_intr(cur_softc); } } /* * This is the meat of the PDMA transfer. * When we get here, we shove data as fast as the mac can take it. * We depend on several things: * * All macs after the Mac Plus that have a 5380 chip should have a general * logic IC that handshakes data for blind transfers. * * If the SCSI controller finishes sending/receiving data before we do, * the same general logic IC will generate a /BERR for us in short order. * * The fault address for said /BERR minus the base address for the * transfer will be the amount of data that was actually written. * * We use the nofault flag and the setjmp/longjmp in locore.s so we can * detect and handle the bus error for early termination of a command. * This is usually caused by a disconnecting target. */ void ncr5380_drq_intr(p) void *p; { #if USE_PDMA extern int *nofault, mac68k_buserr_addr; struct ncr_softc *sc = p; label_t faultbuf; register int count; volatile u_int32_t *long_drq; u_int32_t *long_data; volatile u_int8_t *drq; u_int8_t *data; /* * If we're not ready to xfer data, just return. */ if ( !(GET_5380_REG(NCR5380_DMSTAT) & SC_DMA_REQ) || !pdma_5380_dir) { return; } /* * I don't think this should be necessary, but it is * for writes--at least to some devices. They don't * let go of PH_DATAOUT until we do pdma_cleanup(). */ if (pending_5380_count == 0) { #if NCR5380_PDMA_DEBUG DBG_SET("forcing pdma_cleanup().") #endif pdma_cleanup(); return; } #if NCR5380_PDMA_DEBUG DBG_SET("got drq interrupt.") #endif /* * Setup for a possible bus error caused by SCSI controller * switching out of DATA-IN/OUT before we're done with the * current transfer. */ nofault = (int *) &faultbuf; if (setjmp((label_t *) nofault)) { nofault = (int *) 0; #if NCR5380_PDMA_DEBUG DBG_SET("buserr in xfer.") #endif count = ( (u_long) mac68k_buserr_addr - (u_long) ncr_5380_with_drq); if ((count < 0) || (count > pending_5380_count)) { printf("pdma %s: count = %d (0x%x) (pending " "count %d)\n", (pdma_5380_dir == 2) ? "in" : "out", count, count, pending_5380_count); panic("something is wrong"); } pending_5380_data += count; pending_5380_count -= count; #if NCR5380_PDMA_DEBUG DBG_SET("handled bus error in xfer.") #endif mac68k_buserr_addr = 0; return; } if (pdma_5380_dir == 2) { /* Data In */ int resid; #if NCR5380_PDMA_DEBUG DBG_SET("Data in.") #endif /* * Get the dest address aligned. */ resid = count = min(pending_5380_count, 4 - (((int) pending_5380_data) & 0x3)); if (count && (count < 4)) { #if NCR5380_PDMA_DEBUG DBG_SET("Data in (aligning dest).") #endif data = (u_int8_t *) pending_5380_data; drq = (u_int8_t *) ncr_5380_with_drq; while (count) { #define R1 *data++ = *drq++ R1; count--; #undef R1 } pending_5380_data += resid; pending_5380_count -= resid; } /* * Get ready to start the transfer. */ while (pending_5380_count) { int dcount; #if NCR5380_PDMA_DEBUG DBG_SET("Data in (starting read).") #endif dcount = count = min(pending_5380_count, MIN_PHYS); long_drq = (volatile u_int32_t *) ncr_5380_with_drq; long_data = (u_int32_t *) pending_5380_data; #define R4 *long_data++ = *long_drq++ while ( count >= 512 ) { if (!(GET_5380_REG(NCR5380_DMSTAT) & SC_DMA_REQ)) { nofault = (int *) 0; pending_5380_data += (dcount - count); pending_5380_count -= (dcount - count); #if NCR5380_PDMA_DEBUG DBG_SET("drq low") #endif return; } R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; /* 64 */ R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; /* 128 */ R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; /* 256 */ R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; R4; /* 512 */ count -= 512; } while (count >= 4) { R4; count -= 4; } #undef R4 #if NCR5380_PDMA_DEBUG DBG_SET("Data in (finishing up).") #endif data = (u_int8_t *) long_data; drq = (u_int8_t *) long_drq; while (count) { #define R1 *data++ = *drq++ R1; count--; #undef R1 } pending_5380_count -= dcount; pending_5380_data += dcount; } } else { int resid; #if NCR5380_PDMA_DEBUG DBG_SET("Data out.") #endif /* * Get the source address aligned. */ resid = count = min(pending_5380_count, 4 - (((int) pending_5380_data) & 0x3)); if (count && (count < 4)) { #if NCR5380_PDMA_DEBUG DBG_SET("Data out (aligning dest).") #endif data = (u_int8_t *) pending_5380_data; drq = (u_int8_t *) ncr_5380_with_drq; while (count) { #define W1 *drq++ = *data++ W1; count--; #undef W1 } pending_5380_data += resid; pending_5380_count -= resid; } /* * Get ready to start the transfer. */ while (pending_5380_count) { int dcount; #if NCR5380_PDMA_DEBUG DBG_SET("Data out (starting write).") #endif dcount = count = min(pending_5380_count, MIN_PHYS); long_drq = (volatile u_int32_t *) ncr_5380_with_drq; long_data = (u_int32_t *) pending_5380_data; #define W4 *long_drq++ = *long_data++ while ( count >= 64 ) { W4; W4; W4; W4; W4; W4; W4; W4; W4; W4; W4; W4; W4; W4; W4; W4; /* 64 */ count -= 64; } while (count >= 4) { W4; count -= 4; } #undef W4 #if NCR5380_PDMA_DEBUG DBG_SET("Data out (cleaning up).") #endif data = (u_int8_t *) long_data; drq = (u_int8_t *) long_drq; while (count) { #define W1 *drq++ = *data++ W1; count--; #undef W1 } pending_5380_count -= dcount; pending_5380_data += dcount; } } /* * OK. No bus error occurred above. Clear the nofault flag * so we no longer short-circuit bus errors. */ nofault = (int *) 0; #if NCR5380_PDMA_DEBUG DBG_SET("done in xfer.") #endif #endif /* if USE_PDMA */ } #if USE_PDMA #define SCSI_TIMEOUT_VAL 10000000 static int transfer_pdma(phasep, data, count) u_char *phasep; u_char *data; u_long *count; { SC_REQ *reqp = connected; int len = *count, i, scsi_timeout = SCSI_TIMEOUT_VAL; int s, err; if (pdma_5380_dir) { panic("ncrscsi: transfer_pdma called when operation already " "pending.\n"); } #if NCR5380_PDMA_DEBUG DBG_SET("in transfer_pdma.") #endif /* * Don't bother with PDMA if we can't sleep or for small transfers. */ if (reqp->dr_flag & DRIVER_NOINT) { #if NCR5380_PDMA_DEBUG DBG_SET("pdma, actually using transfer_pio.") #endif transfer_pio(phasep, data, count, 0); return -1; } /* * We are probably already at spl2(), so this is likely a no-op. * Paranoia. */ s = splbio(); scsi_idisable(); /* * Match phases with target. */ SET_5380_REG(NCR5380_TCOM, *phasep); /* * Clear pending interrupts. */ scsi_clr_ipend(); /* * Wait until target asserts BSY. */ while ( ((GET_5380_REG(NCR5380_IDSTAT) & SC_S_BSY) == 0) && (--scsi_timeout) ); if (!scsi_timeout) { #if DIAGNOSTIC printf("scsi timeout: waiting for BSY in %s.\n", (*phasep == PH_DATAOUT) ? "pdma_out" : "pdma_in"); #endif goto scsi_timeout_error; } /* * Tell the driver that we're in DMA mode. */ reqp->dr_flag |= DRIVER_IN_DMA; /* * Load transfer values for DRQ interrupt handlers. */ pending_5380_data = data; pending_5380_count = len; #if NCR5380_PDMA_DEBUG DBG_SET("setting up for interrupt.") #endif /* * Set the transfer function to be called on DRQ interrupts. * And note that we're waiting. */ switch (*phasep) { default: panic("Unexpected phase in transfer_pdma.\n"); case PH_DATAOUT: pdma_5380_dir = 1; SET_5380_REG(NCR5380_ICOM, GET_5380_REG(NCR5380_ICOM)|SC_ADTB); SET_5380_REG(NCR5380_MODE, GET_5380_REG(NCR5380_MODE)|SC_M_DMA); SET_5380_REG(NCR5380_DMSTAT, 0); break; case PH_DATAIN: pdma_5380_dir = 2; SET_5380_REG(NCR5380_ICOM, 0); SET_5380_REG(NCR5380_MODE, GET_5380_REG(NCR5380_MODE)|SC_M_DMA); SET_5380_REG(NCR5380_IRCV, 0); break; } #if NCR5380_PDMA_DEBUG DBG_SET("wait for interrupt.") #endif /* * Now that we're set up, enable interrupts and drop processor * priority back down. */ scsi_ienable(); splx(s); return 0; scsi_timeout_error: /* * Clear the DMA mode. */ SET_5380_REG(NCR5380_MODE, GET_5380_REG(NCR5380_MODE) & ~SC_M_DMA); return -1; } #endif /* if USE_PDMA */ /* Include general routines. */ #include