/* $OpenBSD: spc.c,v 1.13 2008/07/30 18:08:02 miod Exp $ */ /* $NetBSD: spc.c,v 1.2 2003/11/17 14:37:59 tsutsui Exp $ */ /* * Copyright (c) 2003 Izumi Tsutsui. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USELEDS #include #endif int spc_dio_match(struct device *, void *, void *); void spc_dio_attach(struct device *, struct device *, void *); int spc_dio_dmastart(struct spc_softc *, void *, size_t, int); int spc_dio_dmadone(struct spc_softc *); void spc_dio_dmago(void *); void spc_dio_dmastop(void *); int spc_dio_intr(void *); void spc_dio_reset(struct spc_softc *); #define HPSPC_ADDRESS(o) (dsc->sc_dregs + ((o) << 1) + 1) #define hpspc_read(o) *(volatile u_int8_t *)(HPSPC_ADDRESS(o)) #define hpspc_write(o, v) *(volatile u_int8_t *)(HPSPC_ADDRESS(o)) = (v) struct spc_dio_softc { struct spc_softc sc_spc; /* MI spc softc */ struct isr sc_isr; volatile u_int8_t *sc_dregs; /* Complete registers */ struct dmaqueue sc_dq; /* DMA job queue */ u_int sc_dflags; /* DMA flags */ #define SCSI_DMA32 0x01 /* 32-bit DMA should be used */ #define SCSI_HAVEDMA 0x02 /* controller has DMA channel */ #define SCSI_DATAIN 0x04 /* DMA direction */ }; struct cfattach spc_ca = { sizeof(struct spc_dio_softc), spc_dio_match, spc_dio_attach }; struct cfdriver spc_cd = { NULL, "spc", DV_DULL }; /* cf_flags */ #define SPC_NODMA 0x01 int spc_dio_match(struct device *parent, void *vcf, void *aux) { struct dio_attach_args *da = aux; switch (da->da_id) { case DIO_DEVICE_ID_SCSI0: case DIO_DEVICE_ID_SCSI1: case DIO_DEVICE_ID_SCSI2: case DIO_DEVICE_ID_SCSI3: return 1; } return 0; } void spc_dio_attach(struct device *parent, struct device *self, void *aux) { struct spc_dio_softc *dsc = (struct spc_dio_softc *)self; struct spc_softc *sc = &dsc->sc_spc; struct dio_attach_args *da = aux; int ipl; u_int8_t id, hconf; dsc->sc_dregs = (u_int8_t *)iomap(dio_scodetopa(da->da_scode), da->da_size); if (dsc->sc_dregs == NULL) { printf(": can't map SCSI registers\n"); return; } sc->sc_regs = dsc->sc_dregs + SPC_OFFSET; ipl = DIO_IPL(sc->sc_regs); printf(" ipl %d: 98265A SCSI", ipl); hpspc_write(HPSCSI_ID, 0xff); DELAY(100); id = hpspc_read(HPSCSI_ID); hconf = hpspc_read(HPSCSI_HCONF); if ((id & ID_WORD_DMA) == 0) { printf(", 32-bit DMA"); dsc->sc_dflags |= SCSI_DMA32; } if ((hconf & HCONF_PARITY) == 0) printf(", no parity"); printf("\n"); if ((hconf & HCONF_PARITY) != 0) sc->sc_ctlflags = SCTL_PARITY_ENAB; id &= ID_MASK; sc->sc_initiator = id; if ((sc->sc_dev.dv_cfdata->cf_flags & SPC_NODMA) == 0) { sc->sc_dma_start = spc_dio_dmastart; sc->sc_dma_done = spc_dio_dmadone; } sc->sc_reset = spc_dio_reset; dsc->sc_dq.dq_softc = dsc; dsc->sc_dq.dq_start = spc_dio_dmago; dsc->sc_dq.dq_done = spc_dio_dmastop; hpspc_write(HPSCSI_CSR, 0x00); hpspc_write(HPSCSI_HCONF, 0x00); dsc->sc_isr.isr_func = spc_dio_intr; dsc->sc_isr.isr_arg = dsc; dsc->sc_isr.isr_ipl = ipl; dsc->sc_isr.isr_priority = IPL_BIO; dio_intr_establish(&dsc->sc_isr, self->dv_xname); spc_attach(sc); /* Enable SPC interrupts. */ hpspc_write(HPSCSI_CSR, CSR_IE); } int spc_dio_dmastart(struct spc_softc *sc, void *addr, size_t size, int datain) { struct spc_dio_softc *dsc = (struct spc_dio_softc *)sc; /* * The HP98658 hardware cannot do odd length transfers, the * last byte of data will always be 0x00. */ if ((size & 1) != 0) return (EINVAL); dsc->sc_dq.dq_chan = DMA0 | DMA1; dsc->sc_dflags |= SCSI_HAVEDMA; if (datain) dsc->sc_dflags |= SCSI_DATAIN; else dsc->sc_dflags &= ~SCSI_DATAIN; if (dmareq(&dsc->sc_dq) != 0) /* DMA channel is available, so start DMA immediately */ spc_dio_dmago((void *)dsc); /* else dma start function will be called later from dmafree(). */ return (0); } void spc_dio_dmago(void *arg) { struct spc_dio_softc *dsc = (struct spc_dio_softc *)arg; struct spc_softc *sc = &dsc->sc_spc; int len, chan; u_int32_t dmaflags; u_int8_t cmd; hpspc_write(HPSCSI_HCONF, 0); cmd = CSR_IE; dmaflags = DMAGO_NOINT; chan = dsc->sc_dq.dq_chan; if ((dsc->sc_dflags & SCSI_DATAIN) != 0) { cmd |= CSR_DMAIN; dmaflags |= DMAGO_READ; } if ((dsc->sc_dflags & SCSI_DMA32) != 0 && ((u_int)sc->sc_dp & 3) == 0 && (sc->sc_dleft & 3) == 0) { cmd |= CSR_DMA32; dmaflags |= DMAGO_LWORD; } else { dmaflags |= DMAGO_WORD; } sc->sc_flags |= SPC_DOINGDMA; dmago(chan, sc->sc_dp, sc->sc_dleft, dmaflags); hpspc_write(HPSCSI_CSR, cmd); cmd |= (chan == 0) ? CSR_DE0 : CSR_DE1; hpspc_write(HPSCSI_CSR, cmd); cmd = SCMD_XFR; len = sc->sc_dleft; if ((len & (DEV_BSIZE -1)) != 0) { cmd |= SCMD_PAD; #if 0 /* * XXX - If we don't do this, the last 2 or 4 bytes * (depending on word/lword DMA) of a read get trashed. * It looks like it is necessary for the DMA to complete * before the SPC goes into "pad mode"??? Note: if we * also do this on a write, the request never completes. */ if ((dsc->sc_dflags & SCSI_DATAIN) != 0) len += 2; #endif } spc_write(TCH, len >> 16); spc_write(TCM, len >> 8); spc_write(TCL, len); spc_write(PCTL, sc->sc_phase | PCTL_BFINT_ENAB); spc_write(SCMD, cmd); } int spc_dio_dmadone(struct spc_softc *sc) { struct spc_dio_softc *dsc = (struct spc_dio_softc *)sc; int resid, trans; u_int8_t cmd; /* Check if the DMA operation is finished. */ if ((spc_read(SSTS) & SSTS_BUSY) != 0) return (0); sc->sc_flags &= ~SPC_DOINGDMA; if ((dsc->sc_dflags & SCSI_HAVEDMA) != 0) { dsc->sc_dflags &= ~SCSI_HAVEDMA; dmafree(&dsc->sc_dq); } cmd = hpspc_read(HPSCSI_CSR); cmd &= ~(CSR_DE1 | CSR_DE0); hpspc_write(HPSCSI_CSR, cmd); resid = spc_read(TCH) << 16 | spc_read(TCM) << 8 | spc_read(TCL); trans = sc->sc_dleft - resid; sc->sc_dp += trans; sc->sc_dleft -= trans; return (1); } void spc_dio_dmastop(void *arg) { struct spc_dio_softc *dsc = (struct spc_dio_softc *)arg; struct spc_softc *sc = &dsc->sc_spc; u_int8_t cmd; cmd = hpspc_read(HPSCSI_CSR); cmd &= ~(CSR_DE1 | CSR_DE0); hpspc_write(HPSCSI_CSR, cmd); dsc->sc_dflags &= ~SCSI_HAVEDMA; sc->sc_flags &= ~SPC_DOINGDMA; } int spc_dio_intr(void *arg) { struct spc_dio_softc *dsc = (struct spc_dio_softc *)arg; /* if we are sharing the ipl level, this interrupt may not be for us. */ if ((hpspc_read(HPSCSI_CSR) & (CSR_IE | CSR_IR)) != (CSR_IE | CSR_IR)) return (0); #ifdef USELEDS ledcontrol(0, 0, LED_DISK); #endif return (spc_intr(arg)); } void spc_dio_reset(struct spc_softc *sc) { struct spc_dio_softc *dsc = (struct spc_dio_softc *)sc; spc_reset(sc); hpspc_write(HPSCSI_HCONF, 0x00); }