/*- * Copyright (C) 2008-2009 Semihalf * All rights reserved. * * Initial version developed by Ilya Bakulin. Full functionality and bringup * by Piotr Ziecik. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR OR CONTRIBUTORS 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ata_if.h" #include "mvreg.h" #include "mvvar.h" /* Useful macros */ #define EDMA_TIMEOUT 100000 /* 100 ms */ #define SATA_INL(sc, reg) ATA_INL((sc)->sc_mem_res, reg) #define SATA_OUTL(sc, reg, val) ATA_OUTL((sc)->sc_mem_res, reg, val) /* HW-related data structures */ struct sata_prdentry { uint32_t prd_addrlo; uint32_t prd_count; uint32_t prd_addrhi; uint32_t prd_reserved; }; struct sata_crqb { uint32_t crqb_prdlo; uint32_t crqb_prdhi; uint32_t crqb_flags; uint16_t crqb_count; uint16_t crqb_reserved1[2]; uint8_t crqb_ata_command; uint8_t crqb_ata_feature; uint8_t crqb_ata_lba_low; uint8_t crqb_ata_lba_mid; uint8_t crqb_ata_lba_high; uint8_t crqb_ata_device; uint8_t crqb_ata_lba_low_p; uint8_t crqb_ata_lba_mid_p; uint8_t crqb_ata_lba_high_p; uint8_t crqb_ata_feature_p; uint8_t crqb_ata_count; uint8_t crqb_ata_count_p; uint16_t crqb_reserved2; }; struct sata_crpb { uint8_t crpb_tag; uint8_t crpb_reserved; uint8_t crpb_edma_status; uint8_t crpb_dev_status; uint32_t crpb_timestamp; }; /* Identification section. */ struct sata_softc { device_t sc_dev; unsigned int sc_version; unsigned int sc_edma_qlen; uint32_t sc_edma_reqis_mask; uint32_t sc_edma_resos_mask; struct resource *sc_mem_res; bus_space_tag_t sc_mem_res_bustag; bus_space_handle_t sc_mem_res_bushdl; struct resource *sc_irq_res; void *sc_irq_cookiep; struct { void (*function)(void *); void *argument; } sc_interrupt[SATA_CHAN_NUM]; }; /* Controller functions */ static int sata_probe(device_t dev); static int sata_attach(device_t dev); static int sata_detach(device_t dev); static void sata_intr(void*); static struct resource * sata_alloc_resource(device_t dev, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags); static int sata_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r); static int sata_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filt, driver_intr_t *function, void *argument, void **cookiep); static int sata_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie); /* Channel functions */ static int sata_channel_probe(device_t dev); static int sata_channel_attach(device_t dev); static int sata_channel_detach(device_t dev); static int sata_channel_begin_transaction(struct ata_request *request); static int sata_channel_end_transaction(struct ata_request *request); static int sata_channel_status(device_t dev); static int sata_channel_setmode(device_t dev, int target, int mode); static int sata_channel_getrev(device_t dev, int target); static void sata_channel_reset(device_t dev); static void sata_channel_dmasetprd(void *xsc, bus_dma_segment_t *segs, int nsegs, int error); /* EDMA functions */ static int sata_edma_ctrl(device_t dev, int on); static int sata_edma_is_running(device_t); static device_method_t sata_methods[] = { /* Device method */ DEVMETHOD(device_probe, sata_probe), DEVMETHOD(device_attach, sata_attach), DEVMETHOD(device_detach, sata_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* ATA bus methods. */ DEVMETHOD(bus_alloc_resource, sata_alloc_resource), DEVMETHOD(bus_release_resource, sata_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, sata_setup_intr), DEVMETHOD(bus_teardown_intr, sata_teardown_intr), { 0, 0 }, }; static driver_t sata_driver = { "sata", sata_methods, sizeof(struct sata_softc), }; devclass_t sata_devclass; DRIVER_MODULE(sata, simplebus, sata_driver, sata_devclass, 0, 0); MODULE_VERSION(sata, 1); MODULE_DEPEND(sata, ata, 1, 1, 1); static int sata_probe(device_t dev) { struct sata_softc *sc; uint32_t d, r; if (!ofw_bus_is_compatible(dev, "mrvl,sata")) return (ENXIO); soc_id(&d, &r); sc = device_get_softc(dev); switch(d) { case MV_DEV_88F5182: sc->sc_version = 1; sc->sc_edma_qlen = 128; break; case MV_DEV_88F6281: case MV_DEV_88F6282: case MV_DEV_MV78100: case MV_DEV_MV78100_Z0: sc->sc_version = 2; sc->sc_edma_qlen = 32; break; default: device_printf(dev, "unsupported SoC (ID: 0x%08X)!\n", d); return (ENXIO); } sc->sc_edma_reqis_mask = (sc->sc_edma_qlen - 1) << SATA_EDMA_REQIS_OFS; sc->sc_edma_resos_mask = (sc->sc_edma_qlen - 1) << SATA_EDMA_RESOS_OFS; device_set_desc(dev, "Marvell Integrated SATA Controller"); return (0); } static int sata_attach(device_t dev) { struct sata_softc *sc; int mem_id, irq_id, error, i; device_t ata_chan; uint32_t reg; sc = device_get_softc(dev); sc->sc_dev = dev; mem_id = 0; irq_id = 0; /* Allocate resources */ sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &mem_id, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "could not allocate memory.\n"); return (ENOMEM); } sc->sc_mem_res_bustag = rman_get_bustag(sc->sc_mem_res); sc->sc_mem_res_bushdl = rman_get_bushandle(sc->sc_mem_res); KASSERT(sc->sc_mem_res_bustag && sc->sc_mem_res_bushdl, ("cannot get bus handle or tag.")); sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &irq_id, RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(dev, "could not allocate IRQ.\n"); error = ENOMEM; goto err; } error = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE | INTR_ENTROPY, NULL, sata_intr, sc, &sc->sc_irq_cookiep); if (error != 0) { device_printf(dev, "could not setup interrupt.\n"); goto err; } /* Attach channels */ for (i = 0; i < SATA_CHAN_NUM; i++) { ata_chan = device_add_child(dev, "ata", devclass_find_free_unit(ata_devclass, 0)); if (!ata_chan) { device_printf(dev, "cannot add channel %d.\n", i); error = ENOMEM; goto err; } } /* Disable interrupt coalescing */ reg = SATA_INL(sc, SATA_CR); for (i = 0; i < SATA_CHAN_NUM; i++) reg |= SATA_CR_COALDIS(i); /* Disable DMA byte swapping */ if (sc->sc_version == 2) reg |= SATA_CR_NODMABS | SATA_CR_NOEDMABS | SATA_CR_NOPRDPBS; SATA_OUTL(sc, SATA_CR, reg); /* Clear and mask all interrupts */ SATA_OUTL(sc, SATA_ICR, 0); SATA_OUTL(sc, SATA_MIMR, 0); return(bus_generic_attach(dev)); err: sata_detach(dev); return (error); } static int sata_detach(device_t dev) { struct sata_softc *sc; sc = device_get_softc(dev); if (device_is_attached(dev)) bus_generic_detach(dev); if (sc->sc_mem_res != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->sc_mem_res), sc->sc_mem_res); sc->sc_mem_res = NULL; } if (sc->sc_irq_res != NULL) { bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_cookiep); bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->sc_irq_res), sc->sc_irq_res); sc->sc_irq_res = NULL; } return (0); } static struct resource * sata_alloc_resource(device_t dev, device_t child, int type, int *rid, u_long start, u_long end, u_long count, u_int flags) { struct sata_softc *sc; sc = device_get_softc(dev); KASSERT(type == SYS_RES_IRQ && *rid == ATA_IRQ_RID, ("illegal resource request (type %u, rid %u).", type, *rid)); return (sc->sc_irq_res); } static int sata_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { KASSERT(type == SYS_RES_IRQ && rid == ATA_IRQ_RID, ("strange type %u and/or rid %u while releasing resource.", type, rid)); return (0); } static int sata_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filt, driver_intr_t *function, void *argument, void **cookiep) { struct sata_softc *sc; struct ata_channel *ch; sc = device_get_softc(dev); ch = device_get_softc(child); if (filt != NULL) { device_printf(dev, "filter interrupts are not supported.\n"); return (EINVAL); } sc->sc_interrupt[ch->unit].function = function; sc->sc_interrupt[ch->unit].argument = argument; *cookiep = sc; return (0); } static int sata_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { struct sata_softc *sc; struct ata_channel *ch; sc = device_get_softc(dev); ch = device_get_softc(child); sc->sc_interrupt[ch->unit].function = NULL; sc->sc_interrupt[ch->unit].argument = NULL; return (0); } static void sata_intr(void *xsc) { struct sata_softc *sc; int unit; sc = xsc; /* * Behave like ata_generic_intr() for PCI controllers. * Simply invoke ISRs on all channels. */ for (unit = 0; unit < SATA_CHAN_NUM; unit++) if (sc->sc_interrupt[unit].function != NULL) sc->sc_interrupt[unit].function( sc->sc_interrupt[unit].argument); } static int sata_channel_probe(device_t dev) { device_set_desc(dev, "Marvell Integrated SATA Channel"); return (ata_probe(dev)); } static int sata_channel_attach(device_t dev) { struct sata_softc *sc; struct ata_channel *ch; uint64_t work; int error, i; sc = device_get_softc(device_get_parent(dev)); ch = device_get_softc(dev); if (ch->attached) return (0); ch->dev = dev; ch->unit = device_get_unit(dev); ch->flags |= ATA_USE_16BIT | ATA_NO_SLAVE | ATA_SATA; /* Set legacy ATA resources. */ for (i = ATA_DATA; i <= ATA_COMMAND; i++) { ch->r_io[i].res = sc->sc_mem_res; ch->r_io[i].offset = SATA_SHADOWR_BASE(ch->unit) + (i << 2); } ch->r_io[ATA_CONTROL].res = sc->sc_mem_res; ch->r_io[ATA_CONTROL].offset = SATA_SHADOWR_CONTROL(ch->unit); ch->r_io[ATA_IDX_ADDR].res = sc->sc_mem_res; ata_default_registers(dev); /* Set SATA resources. */ ch->r_io[ATA_SSTATUS].res = sc->sc_mem_res; ch->r_io[ATA_SSTATUS].offset = SATA_SATA_SSTATUS(ch->unit); ch->r_io[ATA_SERROR].res = sc->sc_mem_res; ch->r_io[ATA_SERROR].offset = SATA_SATA_SERROR(ch->unit); ch->r_io[ATA_SCONTROL].res = sc->sc_mem_res; ch->r_io[ATA_SCONTROL].offset = SATA_SATA_SCONTROL(ch->unit); ata_generic_hw(dev); ch->hw.begin_transaction = sata_channel_begin_transaction; ch->hw.end_transaction = sata_channel_end_transaction; ch->hw.status = sata_channel_status; /* Set DMA resources */ ata_dmainit(dev); ch->dma.setprd = sata_channel_dmasetprd; /* Clear work area */ KASSERT(sc->sc_edma_qlen * (sizeof(struct sata_crqb) + sizeof(struct sata_crpb)) <= ch->dma.max_iosize, ("insufficient DMA memory for request/response queues.\n")); bzero(ch->dma.work, sc->sc_edma_qlen * (sizeof(struct sata_crqb) + sizeof(struct sata_crpb))); bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* Turn off EDMA engine */ error = sata_edma_ctrl(dev, 0); if (error) { ata_dmafini(dev); return (error); } /* * Initialize EDMA engine: * - Native Command Queuing off, * - Non-Queued operation, * - Host Queue Cache enabled. */ SATA_OUTL(sc, SATA_EDMA_CFG(ch->unit), SATA_EDMA_CFG_HQCACHE | (sc->sc_version == 1) ? SATA_EDMA_CFG_QL128 : 0); /* Set request queue pointers */ work = ch->dma.work_bus; SATA_OUTL(sc, SATA_EDMA_REQBAHR(ch->unit), work >> 32); SATA_OUTL(sc, SATA_EDMA_REQIPR(ch->unit), work & 0xFFFFFFFF); SATA_OUTL(sc, SATA_EDMA_REQOPR(ch->unit), work & 0xFFFFFFFF); /* Set response queue pointers */ work += sc->sc_edma_qlen * sizeof(struct sata_crqb); SATA_OUTL(sc, SATA_EDMA_RESBAHR(ch->unit), work >> 32); SATA_OUTL(sc, SATA_EDMA_RESIPR(ch->unit), work & 0xFFFFFFFF); SATA_OUTL(sc, SATA_EDMA_RESOPR(ch->unit), work & 0xFFFFFFFF); /* Clear any outstanding interrupts */ ATA_IDX_OUTL(ch, ATA_SERROR, ATA_IDX_INL(ch, ATA_SERROR)); SATA_OUTL(sc, SATA_SATA_FISICR(ch->unit), 0); SATA_OUTL(sc, SATA_EDMA_IECR(ch->unit), 0); SATA_OUTL(sc, SATA_ICR, ~(SATA_ICR_DEV(ch->unit) | SATA_ICR_DMADONE(ch->unit))); /* Umask channel interrupts */ SATA_OUTL(sc, SATA_EDMA_IEMR(ch->unit), 0xFFFFFFFF); SATA_OUTL(sc, SATA_MIMR, SATA_INL(sc, SATA_MIMR) | SATA_MICR_DONE(ch->unit) | SATA_MICR_DMADONE(ch->unit) | SATA_MICR_ERR(ch->unit)); ch->attached = 1; return (ata_attach(dev)); } static int sata_channel_detach(device_t dev) { struct sata_softc *sc; struct ata_channel *ch; int error; sc = device_get_softc(device_get_parent(dev)); ch = device_get_softc(dev); if (!ch->attached) return (0); /* Turn off EDMA engine */ sata_edma_ctrl(dev, 0); /* Mask chanel interrupts */ SATA_OUTL(sc, SATA_EDMA_IEMR(ch->unit), 0); SATA_OUTL(sc, SATA_MIMR, SATA_INL(sc, SATA_MIMR) & ~( SATA_MICR_DONE(ch->unit) | SATA_MICR_DMADONE(ch->unit) | SATA_MICR_ERR(ch->unit))); error = ata_detach(dev); ata_dmafini(dev); ch->attached = 0; return (error); } static int sata_channel_begin_transaction(struct ata_request *request) { struct sata_softc *sc; struct ata_channel *ch; struct sata_crqb *crqb; uint32_t req_in; int error, slot; sc = device_get_softc(device_get_parent(request->parent)); ch = device_get_softc(request->parent); mtx_assert(&ch->state_mtx, MA_OWNED); /* Only DMA R/W goes through the EDMA machine. */ if (request->u.ata.command != ATA_READ_DMA && request->u.ata.command != ATA_WRITE_DMA && request->u.ata.command != ATA_READ_DMA48 && request->u.ata.command != ATA_WRITE_DMA48) { /* Disable EDMA before accessing legacy registers */ if (sata_edma_is_running(request->parent)) { error = sata_edma_ctrl(request->parent, 0); if (error) { request->result = error; return (ATA_OP_FINISHED); } } return (ata_begin_transaction(request)); } /* Prepare data for DMA */ if ((error = ch->dma.load(request, NULL, NULL))) { device_printf(request->parent, "setting up DMA failed!\n"); request->result = error; return ATA_OP_FINISHED; } /* Get next free queue slot */ req_in = SATA_INL(sc, SATA_EDMA_REQIPR(ch->unit)); slot = (req_in & sc->sc_edma_reqis_mask) >> SATA_EDMA_REQIS_OFS; crqb = (struct sata_crqb *)(ch->dma.work + (slot << SATA_EDMA_REQIS_OFS)); /* Fill in request */ bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); crqb->crqb_prdlo = htole32((uint64_t)request->dma->sg_bus & 0xFFFFFFFF); crqb->crqb_prdhi = htole32((uint64_t)request->dma->sg_bus >> 32); crqb->crqb_flags = htole32((request->flags & ATA_R_READ ? 0x01 : 0x00) | (request->tag << 1)); crqb->crqb_ata_command = request->u.ata.command; crqb->crqb_ata_feature = request->u.ata.feature; crqb->crqb_ata_lba_low = request->u.ata.lba; crqb->crqb_ata_lba_mid = request->u.ata.lba >> 8; crqb->crqb_ata_lba_high = request->u.ata.lba >> 16; crqb->crqb_ata_device = ((request->u.ata.lba >> 24) & 0x0F) | (1 << 6); crqb->crqb_ata_lba_low_p = request->u.ata.lba >> 24; crqb->crqb_ata_lba_mid_p = request->u.ata.lba >> 32; crqb->crqb_ata_lba_high_p = request->u.ata.lba >> 40; crqb->crqb_ata_feature_p = request->u.ata.feature >> 8; crqb->crqb_ata_count = request->u.ata.count; crqb->crqb_ata_count_p = request->u.ata.count >> 8; bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* Enable EDMA if disabled */ if (!sata_edma_is_running(request->parent)) { error = sata_edma_ctrl(request->parent, 1); if (error) { ch->dma.unload(request); request->result = error; return (ATA_OP_FINISHED); } } /* Tell EDMA about new request */ req_in = (req_in & ~sc->sc_edma_reqis_mask) | (((slot + 1) << SATA_EDMA_REQIS_OFS) & sc->sc_edma_reqis_mask); SATA_OUTL(sc, SATA_EDMA_REQIPR(ch->unit), req_in); return (ATA_OP_CONTINUES); } static int sata_channel_end_transaction(struct ata_request *request) { struct sata_softc *sc; struct ata_channel *ch; struct sata_crpb *crpb; uint32_t res_in, res_out, icr; int slot; sc = device_get_softc(device_get_parent(request->parent)); ch = device_get_softc(request->parent); mtx_assert(&ch->state_mtx, MA_OWNED); icr = SATA_INL(sc, SATA_ICR); if (icr & SATA_ICR_DMADONE(ch->unit)) { /* Get current response slot */ res_out = SATA_INL(sc, SATA_EDMA_RESOPR(ch->unit)); slot = (res_out & sc->sc_edma_resos_mask) >> SATA_EDMA_RESOS_OFS; crpb = (struct sata_crpb *)(ch->dma.work + (sc->sc_edma_qlen * sizeof(struct sata_crqb)) + (slot << SATA_EDMA_RESOS_OFS)); /* Record this request status */ bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); request->status = crpb->crpb_dev_status; request->error = 0; bus_dmamap_sync(ch->dma.work_tag, ch->dma.work_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* Update response queue pointer */ res_out = (res_out & ~sc->sc_edma_resos_mask) | (((slot + 1) << SATA_EDMA_RESOS_OFS) & sc->sc_edma_resos_mask); SATA_OUTL(sc, SATA_EDMA_RESOPR(ch->unit), res_out); /* Ack DMA interrupt if there is nothing more to do */ res_in = SATA_INL(sc, SATA_EDMA_RESIPR(ch->unit)); res_in &= sc->sc_edma_resos_mask; res_out &= sc->sc_edma_resos_mask; if (res_in == res_out) SATA_OUTL(sc, SATA_ICR, ~SATA_ICR_DMADONE(ch->unit)); /* Update progress */ if (!(request->status & ATA_S_ERROR) && !(request->flags & ATA_R_TIMEOUT)) request->donecount = request->bytecount; /* Unload DMA data */ ch->dma.unload(request); return(ATA_OP_FINISHED); } /* Legacy ATA interrupt */ return (ata_end_transaction(request)); } static int sata_channel_status(device_t dev) { struct sata_softc *sc; struct ata_channel *ch; uint32_t icr, iecr; sc = device_get_softc(device_get_parent(dev)); ch = device_get_softc(dev); icr = SATA_INL(sc, SATA_ICR); iecr = SATA_INL(sc, SATA_EDMA_IECR(ch->unit)); if ((icr & SATA_ICR_DEV(ch->unit)) || iecr) { /* Disable EDMA before accessing SATA registers */ sata_edma_ctrl(dev, 0); ata_sata_phy_check_events(dev, -1); /* Ack device and error interrupt */ SATA_OUTL(sc, SATA_ICR, ~SATA_ICR_DEV(ch->unit)); SATA_OUTL(sc, SATA_EDMA_IECR(ch->unit), 0); } icr &= SATA_ICR_DEV(ch->unit) | SATA_ICR_DMADONE(ch->unit); return (icr); } static void sata_channel_reset(device_t dev) { struct sata_softc *sc; struct ata_channel *ch; sc = device_get_softc(device_get_parent(dev)); ch = device_get_softc(dev); /* Disable EDMA before using legacy registers */ sata_edma_ctrl(dev, 0); /* Mask all EDMA interrups */ SATA_OUTL(sc, SATA_EDMA_IEMR(ch->unit), 0); /* Reset EDMA */ SATA_OUTL(sc, SATA_EDMA_CMD(ch->unit), SATA_EDMA_CMD_RESET); DELAY(25); SATA_OUTL(sc, SATA_EDMA_CMD(ch->unit), 0); /* Reset PHY and device */ if (ata_sata_phy_reset(dev, -1, 1)) ata_generic_reset(dev); else ch->devices = 0; /* Clear EDMA errors */ SATA_OUTL(sc, SATA_SATA_FISICR(ch->unit), 0); SATA_OUTL(sc, SATA_EDMA_IECR(ch->unit), 0); /* Unmask all EDMA interrups */ SATA_OUTL(sc, SATA_EDMA_IEMR(ch->unit), 0xFFFFFFFF); } static int sata_channel_setmode(device_t parent, int target, int mode) { /* Disable EDMA before using legacy registers */ sata_edma_ctrl(parent, 0); return (ata_sata_setmode(parent, target, mode)); } static int sata_channel_getrev(device_t parent, int target) { /* Disable EDMA before using legacy registers */ sata_edma_ctrl(parent, 0); return (ata_sata_getrev(parent, target)); } static void sata_channel_dmasetprd(void *xsc, bus_dma_segment_t *segs, int nsegs, int error) { struct ata_dmasetprd_args *args; struct sata_prdentry *prd; int i; args = xsc; prd = args->dmatab; if ((args->error = error)) return; for (i = 0; i < nsegs; i++) { prd[i].prd_addrlo = htole32(segs[i].ds_addr); prd[i].prd_addrhi = htole32((uint64_t)segs[i].ds_addr >> 32); prd[i].prd_count = htole32(segs[i].ds_len); } prd[i - 1].prd_count |= htole32(ATA_DMA_EOT); KASSERT(nsegs <= ATA_DMA_ENTRIES, ("too many DMA segment entries.\n")); args->nsegs = nsegs; } static int sata_edma_ctrl(device_t dev, int on) { struct sata_softc *sc; struct ata_channel *ch; int bit, timeout; uint32_t reg; sc = device_get_softc(device_get_parent(dev)); ch = device_get_softc(dev); bit = on ? SATA_EDMA_CMD_ENABLE : SATA_EDMA_CMD_DISABLE; timeout = EDMA_TIMEOUT; SATA_OUTL(sc, SATA_EDMA_CMD(ch->unit), bit); while (1) { DELAY(1); reg = SATA_INL(sc, SATA_EDMA_CMD(ch->unit)); /* Enable bit will be 1 after disable command completion */ if (on && (reg & SATA_EDMA_CMD_ENABLE)) break; /* Disable bit will be 0 after disable command completion */ if (!on && !(reg & SATA_EDMA_CMD_DISABLE)) break; if (timeout-- <= 0) { device_printf(dev, "EDMA command timeout!\n"); return (ETIMEDOUT); } } return (0); } static int sata_edma_is_running(device_t dev) { struct sata_softc *sc; struct ata_channel *ch; sc = device_get_softc(device_get_parent(dev)); ch = device_get_softc(dev); return (SATA_INL(sc, SATA_EDMA_CMD(ch->unit)) & SATA_EDMA_CMD_ENABLE); } static device_method_t sata_channel_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, sata_channel_probe), DEVMETHOD(device_attach, sata_channel_attach), DEVMETHOD(device_detach, sata_channel_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, ata_suspend), DEVMETHOD(device_resume, ata_resume), /* ATA channel interface */ DEVMETHOD(ata_reset, sata_channel_reset), DEVMETHOD(ata_setmode, sata_channel_setmode), DEVMETHOD(ata_getrev, sata_channel_getrev), { 0, 0 } }; driver_t sata_channel_driver = { "ata", sata_channel_methods, sizeof(struct ata_channel), }; DRIVER_MODULE(ata, sata, sata_channel_driver, ata_devclass, 0, 0);