/* $NetBSD: spifi.c,v 1.9 2002/10/02 04:27:51 thorpej Exp $ */ /*- * Copyright (c) 2000 Tsubai Masanari. 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 /* #define SPIFI_DEBUG */ #ifdef SPIFI_DEBUG # define DPRINTF printf #else # define DPRINTF while (0) printf #endif struct spifi_scb { TAILQ_ENTRY(spifi_scb) chain; int flags; struct scsipi_xfer *xs; struct scsi_generic cmd; int cmdlen; int resid; vaddr_t daddr; u_char target; u_char lun; u_char lun_targ; u_char status; }; /* scb flags */ #define SPIFI_READ 0x80 #define SPIFI_DMA 0x01 struct spifi_softc { struct device sc_dev; struct scsipi_channel sc_channel; struct scsipi_adapter sc_adapter; struct spifi_reg *sc_reg; struct spifi_scb *sc_nexus; void *sc_dma; /* attached DMA softc */ int sc_id; /* my SCSI ID */ int sc_msgout; u_char sc_omsg[16]; struct spifi_scb sc_scb[16]; TAILQ_HEAD(, spifi_scb) free_scb; TAILQ_HEAD(, spifi_scb) ready_scb; }; #define SPIFI_SYNC_OFFSET_MAX 7 #define SEND_REJECT 1 #define SEND_IDENTIFY 2 #define SEND_SDTR 4 #define SPIFI_DATAOUT 0 #define SPIFI_DATAIN PRS_IO #define SPIFI_COMMAND PRS_CD #define SPIFI_STATUS (PRS_CD | PRS_IO) #define SPIFI_MSGOUT (PRS_MSG | PRS_CD) #define SPIFI_MSGIN (PRS_MSG | PRS_CD | PRS_IO) int spifi_match(struct device *, struct cfdata *, void *); void spifi_attach(struct device *, struct device *, void *); void spifi_scsipi_request(struct scsipi_channel *, scsipi_adapter_req_t, void *); struct spifi_scb *spifi_get_scb(struct spifi_softc *); void spifi_free_scb(struct spifi_softc *, struct spifi_scb *); int spifi_poll(struct spifi_softc *); void spifi_minphys(struct buf *); void spifi_sched(struct spifi_softc *); int spifi_intr(void *); void spifi_pmatch(struct spifi_softc *); void spifi_select(struct spifi_softc *); void spifi_sendmsg(struct spifi_softc *, int); void spifi_command(struct spifi_softc *); void spifi_data_io(struct spifi_softc *); void spifi_status(struct spifi_softc *); int spifi_done(struct spifi_softc *); void spifi_fifo_drain(struct spifi_softc *); void spifi_reset(struct spifi_softc *); void spifi_bus_reset(struct spifi_softc *); static int spifi_read_count(struct spifi_reg *); static void spifi_write_count(struct spifi_reg *, int); #define DMAC3_FASTACCESS(sc) dmac3_misc((sc)->sc_dma, DMAC3_CONF_FASTACCESS) #define DMAC3_SLOWACCESS(sc) dmac3_misc((sc)->sc_dma, DMAC3_CONF_SLOWACCESS) CFATTACH_DECL(spifi, sizeof(struct spifi_softc), spifi_match, spifi_attach, NULL, NULL); int spifi_match(parent, cf, aux) struct device *parent; struct cfdata *cf; void *aux; { struct apbus_attach_args *apa = aux; if (strcmp(apa->apa_name, "spifi") == 0) return 1; return 0; } void spifi_attach(parent, self, aux) struct device *parent, *self; void *aux; { struct spifi_softc *sc = (void *)self; struct apbus_attach_args *apa = aux; struct device *dma; int intr, i; /* Initialize scbs. */ TAILQ_INIT(&sc->free_scb); TAILQ_INIT(&sc->ready_scb); for (i = 0; i < sizeof(sc->sc_scb)/sizeof(sc->sc_scb[0]); i++) TAILQ_INSERT_TAIL(&sc->free_scb, &sc->sc_scb[i], chain); sc->sc_reg = (struct spifi_reg *)apa->apa_hwbase; sc->sc_id = 7; /* XXX */ /* Find my dmac3. */ dma = dmac3_link(apa->apa_ctlnum); if (dma == NULL) { printf(": cannot find slave dmac\n"); return; } sc->sc_dma = dma; printf(" slot%d addr 0x%lx", apa->apa_slotno, apa->apa_hwbase); printf(": SCSI ID = %d, using %s\n", sc->sc_id, dma->dv_xname); dmac3_reset(sc->sc_dma); DMAC3_SLOWACCESS(sc); spifi_reset(sc); DMAC3_FASTACCESS(sc); sc->sc_adapter.adapt_dev = &sc->sc_dev; sc->sc_adapter.adapt_nchannels = 1; sc->sc_adapter.adapt_openings = 7; sc->sc_adapter.adapt_max_periph = 1; sc->sc_adapter.adapt_ioctl = NULL; sc->sc_adapter.adapt_minphys = minphys; sc->sc_adapter.adapt_request = spifi_scsipi_request; memset(&sc->sc_channel, 0, sizeof(sc->sc_channel)); sc->sc_channel.chan_adapter = &sc->sc_adapter; sc->sc_channel.chan_bustype = &scsi_bustype; sc->sc_channel.chan_channel = 0; sc->sc_channel.chan_ntargets = 8; sc->sc_channel.chan_nluns = 8; sc->sc_channel.chan_id = sc->sc_id; if (apa->apa_slotno == 0) intr = NEWS5000_INT0_DMAC; else intr = SLOTTOMASK(apa->apa_slotno); apbus_intr_establish(0, intr, 0, spifi_intr, sc, apa->apa_name, apa->apa_ctlnum); config_found(&sc->sc_dev, &sc->sc_channel, scsiprint); } void spifi_scsipi_request(chan, req, arg) struct scsipi_channel *chan; scsipi_adapter_req_t req; void *arg; { struct scsipi_xfer *xs; struct scsipi_periph *periph; struct spifi_softc *sc = (void *)chan->chan_adapter->adapt_dev; struct spifi_scb *scb; u_int flags; int s; switch (req) { case ADAPTER_REQ_RUN_XFER: xs = arg; periph = xs->xs_periph; DPRINTF("spifi_scsi_cmd\n"); flags = xs->xs_control; scb = spifi_get_scb(sc); if (scb == NULL) { panic("spifi_scsipi_request: no scb"); } scb->xs = xs; scb->flags = 0; scb->status = 0; scb->daddr = (vaddr_t)xs->data; scb->resid = xs->datalen; bcopy(xs->cmd, &scb->cmd, xs->cmdlen); scb->cmdlen = xs->cmdlen; scb->target = periph->periph_target; scb->lun = periph->periph_lun; scb->lun_targ = scb->target | (scb->lun << 3); if (flags & XS_CTL_DATA_IN) scb->flags |= SPIFI_READ; s = splbio(); TAILQ_INSERT_TAIL(&sc->ready_scb, scb, chain); if (sc->sc_nexus == NULL) /* IDLE */ spifi_sched(sc); splx(s); if (flags & XS_CTL_POLL) { if (spifi_poll(sc)) { printf("spifi: timeout\n"); if (spifi_poll(sc)) printf("spifi: timeout again\n"); } } return; case ADAPTER_REQ_GROW_RESOURCES: /* XXX Not supported. */ return; case ADAPTER_REQ_SET_XFER_MODE: /* XXX Not supported. */ return; } } struct spifi_scb * spifi_get_scb(sc) struct spifi_softc *sc; { struct spifi_scb *scb; int s; s = splbio(); scb = sc->free_scb.tqh_first; if (scb) TAILQ_REMOVE(&sc->free_scb, scb, chain); splx(s); return scb; } void spifi_free_scb(sc, scb) struct spifi_softc *sc; struct spifi_scb *scb; { int s; s = splbio(); TAILQ_INSERT_HEAD(&sc->free_scb, scb, chain); splx(s); } int spifi_poll(sc) struct spifi_softc *sc; { struct spifi_scb *scb = sc->sc_nexus; struct scsipi_xfer *xs; int count; printf("spifi_poll: not implemented yet\n"); delay(10000); scb->status = SCSI_OK; scb->resid = 0; spifi_done(sc); return 0; if (xs == NULL) return 0; xs = scb->xs; count = xs->timeout; while (count > 0) { if (dmac3_intr(sc->sc_dma) != 0) spifi_intr(sc); if (xs->xs_status & XS_STS_DONE) return 0; DELAY(1000); count--; }; return 1; } void spifi_minphys(bp) struct buf *bp; { if (bp->b_bcount > 64*1024) bp->b_bcount = 64*1024; minphys(bp); } void spifi_sched(sc) struct spifi_softc *sc; { struct spifi_scb *scb; scb = sc->ready_scb.tqh_first; start: if (scb == NULL || sc->sc_nexus != NULL) return; /* if (sc->sc_targets[scb->target] & (1 << scb->lun)) goto next; */ TAILQ_REMOVE(&sc->ready_scb, scb, chain); #ifdef SPIFI_DEBUG { int i; printf("spifi_sched: ID:LUN = %d:%d, ", scb->target, scb->lun); printf("cmd = 0x%x", scb->cmd.opcode); for (i = 0; i < 5; i++) printf(" 0x%x", scb->cmd.bytes[i]); printf("\n"); } #endif DMAC3_SLOWACCESS(sc); sc->sc_nexus = scb; spifi_select(sc); DMAC3_FASTACCESS(sc); scb = scb->chain.tqe_next; goto start; } static inline int spifi_read_count(reg) struct spifi_reg *reg; { int count; count = (reg->count_hi & 0xff) << 16 | (reg->count_mid & 0xff) << 8 | (reg->count_low & 0xff); return count; } static inline void spifi_write_count(reg, count) struct spifi_reg *reg; int count; { reg->count_hi = count >> 16; reg->count_mid = count >> 8; reg->count_low = count; } #ifdef SPIFI_DEBUG static char scsi_phase_name[][8] = { "DATAOUT", "DATAIN", "COMMAND", "STATUS", "", "", "MSGOUT", "MSGIN" }; #endif int spifi_intr(v) void *v; { struct spifi_softc *sc = v; struct spifi_reg *reg = sc->sc_reg; int intr, state, icond; struct spifi_scb *scb; struct scsipi_xfer *xs; #ifdef SPIFI_DEBUG char bitmask[64]; #endif switch (dmac3_intr(sc->sc_dma)) { case 0: DPRINTF("spurious dma intr\n"); return 0; case -1: printf("DMAC parity error, data PAD\n"); DMAC3_SLOWACCESS(sc); reg->prcmd = PRC_TRPAD; DMAC3_FASTACCESS(sc); return 1; default: break; } DMAC3_SLOWACCESS(sc); intr = reg->intr & 0xff; if (intr == 0) { DMAC3_FASTACCESS(sc); DPRINTF("spurious intr (not me)\n"); return 0; } scb = sc->sc_nexus; xs = scb->xs; state = reg->spstat; icond = reg->icond; /* clear interrupt */ reg->intr = ~intr; #ifdef SPIFI_DEBUG bitmask_snprintf(intr, INTR_BITMASK, bitmask, sizeof bitmask); printf("spifi_intr intr = 0x%s (%s), ", bitmask, scsi_phase_name[(reg->prstat >> 3) & 7]); printf("state = 0x%x, icond = 0x%x\n", state, icond); #endif if (intr & INTR_FCOMP) { spifi_fifo_drain(sc); scb->status = reg->cmbuf[scb->target].status; scb->resid = spifi_read_count(reg); DPRINTF("datalen = %d, resid = %d, status = 0x%x\n", xs->datalen, scb->resid, scb->status); DPRINTF("msg = 0x%x\n", reg->cmbuf[sc->sc_id].cdb[0]); DMAC3_FASTACCESS(sc); spifi_done(sc); return 1; } if (intr & INTR_DISCON) panic("disconnect"); if (intr & INTR_TIMEO) { xs->error = XS_SELTIMEOUT; DMAC3_FASTACCESS(sc); spifi_done(sc); return 1; } if (intr & INTR_BSRQ) { if (scb == NULL) panic("reconnect?"); if (intr & INTR_PERR) { printf("%s: %d:%d parity error\n", sc->sc_dev.dv_xname, scb->target, scb->lun); /* XXX reset */ xs->error = XS_DRIVER_STUFFUP; spifi_done(sc); return 1; } if (state >> 4 == SPS_MSGIN && icond == ICOND_NXTREQ) panic("spifi_intr: NXTREQ"); if (reg->fifoctrl & FIFOC_RQOVRN) panic("spifi_intr RQOVRN"); if (icond == ICOND_UXPHASEZ) panic("ICOND_UXPHASEZ"); if ((icond & 0x0f) == ICOND_ADATAOFF) { spifi_data_io(sc); goto done; } if ((icond & 0xf0) == ICOND_UBF) { reg->exstat = reg->exstat & ~EXS_UBF; spifi_pmatch(sc); goto done; } /* * XXX Work around the SPIFI bug that interrupts during * XXX dataout phase. */ if (state == ((SPS_DATAOUT << 4) | SPS_INTR) && (reg->prstat & PRS_PHASE) == SPIFI_DATAOUT) { reg->prcmd = PRC_DATAOUT; goto done; } if ((reg->prstat & PRS_Z) == 0) { spifi_pmatch(sc); goto done; } panic("spifi_intr: unknown intr state"); } done: DMAC3_FASTACCESS(sc); return 1; } void spifi_pmatch(sc) struct spifi_softc *sc; { struct spifi_reg *reg = sc->sc_reg; int phase; phase = (reg->prstat & PRS_PHASE); #ifdef SPIFI_DEBUG printf("spifi_pmatch (%s)\n", scsi_phase_name[phase >> 3]); #endif switch (phase) { case SPIFI_COMMAND: spifi_command(sc); break; case SPIFI_DATAIN: case SPIFI_DATAOUT: spifi_data_io(sc); break; case SPIFI_STATUS: spifi_status(sc); break; case SPIFI_MSGIN: /* XXX */ case SPIFI_MSGOUT: /* XXX */ default: printf("spifi: unknown phase %d\n", phase); } } void spifi_select(sc) struct spifi_softc *sc; { struct spifi_reg *reg = sc->sc_reg; struct spifi_scb *scb = sc->sc_nexus; int sel; #if 0 if (reg->loopdata || reg->intr) return; #endif if (scb == NULL) { printf("%s: spifi_select: NULL nexus\n", sc->sc_dev.dv_xname); return; } reg->exctrl = EXC_IPLOCK; dmac3_reset(sc->sc_dma); sel = scb->target << 4 | SEL_ISTART | SEL_IRESELEN | SEL_WATN; spifi_sendmsg(sc, SEND_IDENTIFY); reg->select = sel; } void spifi_sendmsg(sc, msg) struct spifi_softc *sc; int msg; { struct spifi_scb *scb = sc->sc_nexus; /* struct mesh_tinfo *ti; */ int lun, len, i; int id = sc->sc_id; struct spifi_reg *reg = sc->sc_reg; DPRINTF("spifi_sendmsg: sending"); sc->sc_msgout = msg; len = 0; if (msg & SEND_REJECT) { DPRINTF(" REJECT"); sc->sc_omsg[len++] = MSG_MESSAGE_REJECT; } if (msg & SEND_IDENTIFY) { DPRINTF(" IDENTIFY"); lun = scb->xs->xs_periph->periph_lun; sc->sc_omsg[len++] = MSG_IDENTIFY(lun, 0); } if (msg & SEND_SDTR) { DPRINTF(" SDTR"); #if 0 ti = &sc->sc_tinfo[scb->target]; sc->sc_omsg[len++] = MSG_EXTENDED; sc->sc_omsg[len++] = 3; sc->sc_omsg[len++] = MSG_EXT_SDTR; sc->sc_omsg[len++] = ti->period; sc->sc_omsg[len++] = ti->offset; #endif } DPRINTF("\n"); reg->cmlen = CML_AMSG_EN | len; for (i = 0; i < len; i++) reg->cmbuf[id].cdb[i] = sc->sc_omsg[i]; } void spifi_command(struct spifi_softc *sc) { struct spifi_scb *scb = sc->sc_nexus; struct spifi_reg *reg = sc->sc_reg; int len = scb->cmdlen; u_char *cmdp = (char *)&scb->cmd; int i; DPRINTF("spifi_command\n"); reg->cmdpage = scb->lun_targ; if (reg->init_status & IST_ACK) { /* Negate ACK. */ reg->prcmd = PRC_NJMP | PRC_CLRACK | PRC_COMMAND; reg->prcmd = PRC_NJMP | PRC_COMMAND; } reg->cmlen = CML_AMSG_EN | len; for (i = 0; i < len; i++) reg->cmbuf[sc->sc_id].cdb[i] = *cmdp++; reg->prcmd = PRC_COMMAND; } void spifi_data_io(struct spifi_softc *sc) { struct spifi_scb *scb = sc->sc_nexus; struct spifi_reg *reg = sc->sc_reg; int phase; DPRINTF("spifi_data_io\n"); phase = reg->prstat & PRS_PHASE; dmac3_reset(sc->sc_dma); spifi_write_count(reg, scb->resid); reg->cmlen = CML_AMSG_EN | 1; reg->data_xfer = 0; scb->flags |= SPIFI_DMA; if (phase == SPIFI_DATAIN) { if (reg->fifoctrl & FIFOC_SSTKACT) { /* * Clear FIFO and load the contents of synchronous * stack into the FIFO. */ reg->fifoctrl = FIFOC_CLREVEN; reg->fifoctrl = FIFOC_LOAD; } reg->autodata = ADATA_IN | scb->lun_targ; dmac3_start(sc->sc_dma, scb->daddr, scb->resid, DMAC3_CSR_RECV); reg->prcmd = PRC_DATAIN; } else { reg->fifoctrl = FIFOC_CLREVEN; reg->autodata = scb->lun_targ; dmac3_start(sc->sc_dma, scb->daddr, scb->resid, DMAC3_CSR_SEND); reg->prcmd = PRC_DATAOUT; } } void spifi_status(struct spifi_softc *sc) { struct spifi_reg *reg = sc->sc_reg; DPRINTF("spifi_status\n"); spifi_fifo_drain(sc); reg->cmlen = CML_AMSG_EN | 1; reg->prcmd = PRC_STATUS; } int spifi_done(sc) struct spifi_softc *sc; { struct spifi_scb *scb = sc->sc_nexus; struct scsipi_xfer *xs = scb->xs; DPRINTF("spifi_done\n"); xs->status = scb->status; if (xs->status == SCSI_CHECK) { DPRINTF("spifi_done: CHECK CONDITION\n"); if (xs->error == XS_NOERROR) xs->error = XS_BUSY; } xs->resid = scb->resid; scsipi_done(xs); spifi_free_scb(sc, scb); sc->sc_nexus = NULL; spifi_sched(sc); return FALSE; } void spifi_fifo_drain(sc) struct spifi_softc *sc; { struct spifi_scb *scb = sc->sc_nexus; struct spifi_reg *reg = sc->sc_reg; int fifoctrl, fifo_count; DPRINTF("spifi_fifo_drain\n"); if ((scb->flags & SPIFI_READ) == 0) return; fifoctrl = reg->fifoctrl; if (fifoctrl & FIFOC_SSTKACT) return; fifo_count = 8 - (fifoctrl & FIFOC_FSLOT); if (fifo_count > 0 && (scb->flags & SPIFI_DMA)) { /* Flush data still in FIFO. */ reg->fifoctrl = FIFOC_FLUSH; return; } reg->fifoctrl = FIFOC_CLREVEN; } void spifi_reset(sc) struct spifi_softc *sc; { struct spifi_reg *reg = sc->sc_reg; int id = sc->sc_id; DPRINTF("spifi_reset\n"); reg->auxctrl = AUXCTRL_SRST; reg->auxctrl = AUXCTRL_CRST; dmac3_reset(sc->sc_dma); reg->auxctrl = AUXCTRL_SRST; reg->auxctrl = AUXCTRL_CRST; reg->auxctrl = AUXCTRL_DMAEDGE; /* Mask (only) target mode interrupts. */ reg->imask = INTR_TGSEL | INTR_COMRECV; reg->config = CONFIG_DMABURST | CONFIG_PCHKEN | CONFIG_PGENEN | id; reg->fastwide = FAST_FASTEN; reg->prctrl = 0; reg->loopctrl = 0; /* Enable automatic status input except the initiator. */ reg->autostat = ~(1 << id); reg->fifoctrl = FIFOC_CLREVEN; spifi_write_count(reg, 0); /* Flush write buffer. */ (void)reg->spstat; } void spifi_bus_reset(sc) struct spifi_softc *sc; { struct spifi_reg *reg = sc->sc_reg; printf("%s: bus reset\n", sc->sc_dev.dv_xname); sc->sc_nexus = NULL; reg->auxctrl = AUXCTRL_SETRST; delay(100); reg->auxctrl = 0; } #if 0 static u_char spifi_sync_period[] = { /* 0 1 2 3 4 5 6 7 8 9 10 11 */ 137, 125, 112, 100, 87, 75, 62, 50, 43, 37, 31, 25 }; void spifi_setsync(sc, ti) struct spifi_softc *sc; struct spifi_tinfo *ti; { if ((ti->flags & T_SYNCMODE) == 0) reg->data_xfer = 0; else { int period = ti->period; int offset = ti->offset; int v; for (v = sizeof(spifi_sync_period) - 1; v >= 0; v--) if (spifi_sync_period[v] >= period) break; if (v == -1) reg->data_xfer = 0; /* XXX */ else reg->data_xfer = v << 4 | offset; } } #endif