/*- * Copyright (c) 2002, 2003 Søren Schmidt * 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, * without modification, immediately at the beginning of the file. * 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. * * $FreeBSD$ */ #include "opt_ata.h" #include #include #include #include #include #include #include #include #include #include #include /* local vars */ static bus_addr_t ata_pc98_ports[] = { 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe }; struct ata_cbus_controller { struct resource *io; struct resource *altio; struct resource *bankio; struct resource *irq; void *ih; void (*setmode)(struct ata_device *, int); void (*locking)(struct ata_channel *, int); int current_bank; struct { void (*function)(void *); void *argument; } interrupt[2]; }; /* local prototypes */ static void ata_cbus_intr(void *); static void ata_cbus_banking(struct ata_channel *, int); static void ata_cbus_setmode(struct ata_device *, int); static int ata_cbus_probe(device_t dev) { struct resource *io; int rid; u_long tmp; /* dont probe PnP devices */ if (isa_get_vendorid(dev)) return (ENXIO); /* allocate the ioport range */ rid = ATA_IOADDR_RID; io = isa_alloc_resourcev(dev, SYS_RES_IOPORT, &rid, ata_pc98_ports, ATA_IOSIZE, RF_ACTIVE); if (!io) return ENOMEM; isa_load_resourcev(io, ata_pc98_ports, ATA_IOSIZE); /* calculate & set the altport range */ rid = ATA_PC98_ALTADDR_RID; if (bus_get_resource(dev, SYS_RES_IOPORT, rid, &tmp, &tmp)) { bus_set_resource(dev, SYS_RES_IOPORT, rid, rman_get_start(io)+ATA_PC98_ALTOFFSET, ATA_ALTIOSIZE); } /* calculate & set the bank range */ rid = ATA_PC98_BANKADDR_RID; if (bus_get_resource(dev, SYS_RES_IOPORT, rid, &tmp, &tmp)) { bus_set_resource(dev, SYS_RES_IOPORT, rid, ATA_PC98_BANK, ATA_PC98_BANKIOSIZE); } bus_release_resource(dev, SYS_RES_IOPORT, ATA_IOADDR_RID, io); return 0; } static int ata_cbus_attach(device_t dev) { struct ata_cbus_controller *ctlr = device_get_softc(dev); int rid; /* allocate resources */ rid = ATA_IOADDR_RID; ctlr->io = isa_alloc_resourcev(dev, SYS_RES_IOPORT, &rid, ata_pc98_ports, ATA_IOSIZE, RF_ACTIVE); if (!ctlr->io) return ENOMEM; isa_load_resourcev(ctlr->io, ata_pc98_ports, ATA_IOSIZE); rid = ATA_PC98_ALTADDR_RID; ctlr->altio = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, rman_get_start(ctlr->io)+ATA_PC98_ALTOFFSET, ~0, ATA_ALTIOSIZE, RF_ACTIVE); if (!ctlr->altio) return ENOMEM; rid = ATA_PC98_BANKADDR_RID; ctlr->bankio = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, ATA_PC98_BANK, ~0, ATA_PC98_BANKIOSIZE, RF_ACTIVE); if (!ctlr->bankio) return ENOMEM; rid = 0; if (!(ctlr->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE))) { device_printf(dev, "unable to alloc interrupt\n"); return ENXIO; } if ((bus_setup_intr(dev, ctlr->irq, INTR_TYPE_BIO | INTR_ENTROPY, ata_cbus_intr, ctlr, &ctlr->ih))) { device_printf(dev, "unable to setup interrupt\n"); return ENXIO; } ctlr->locking = ata_cbus_banking; ctlr->current_bank = -1; ctlr->setmode = ata_cbus_setmode;; if (!device_add_child(dev, "ata", 0)) return ENOMEM; if (!device_add_child(dev, "ata", 1)) return ENOMEM; return bus_generic_attach(dev); } static struct resource * ata_cbus_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 ata_cbus_controller *ctlr = device_get_softc(dev); if (type == SYS_RES_IOPORT) { switch (*rid) { case ATA_IOADDR_RID: return ctlr->io; case ATA_ALTADDR_RID: return ctlr->altio; } } if (type == SYS_RES_IRQ) { return ctlr->irq; } return 0; } static int ata_cbus_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_intr_t *intr, void *arg, void **cookiep) { struct ata_cbus_controller *controller = device_get_softc(dev); int unit = ((struct ata_channel *)device_get_softc(child))->unit; controller->interrupt[unit].function = intr; controller->interrupt[unit].argument = arg; *cookiep = controller; return 0; } static int ata_cbus_print_child(device_t dev, device_t child) { struct ata_channel *ch = device_get_softc(child); int retval = 0; retval += bus_print_child_header(dev, child); retval += printf(" at bank %d", ch->unit); retval += bus_print_child_footer(dev, child); return retval; } static void ata_cbus_intr(void *data) { struct ata_cbus_controller *ctlr = data; if (ctlr->current_bank != -1 && ctlr->interrupt[ctlr->current_bank].argument) ctlr->interrupt[ctlr->current_bank]. function(ctlr->interrupt[ctlr->current_bank].argument); } static void ata_cbus_banking(struct ata_channel *ch, int flags) { struct ata_cbus_controller *ctlr = device_get_softc(device_get_parent(ch->dev)); switch (flags) { case ATA_LF_LOCK: if (ctlr->current_bank == ch->unit) break; while (!atomic_cmpset_acq_int(&ctlr->current_bank, -1, ch->unit)) tsleep((caddr_t)ch->locking, PRIBIO, "atalck", 1); bus_space_write_1(rman_get_bustag(ctlr->bankio), rman_get_bushandle(ctlr->bankio), 0, ch->unit); break; case ATA_LF_UNLOCK: if (ctlr->current_bank == -1 || ctlr->current_bank != ch->unit) break; atomic_store_rel_int(&ctlr->current_bank, -1); break; } return; } static void ata_cbus_setmode(struct ata_device *atadev, int mode) { atadev->mode = ata_limit_mode(atadev, mode, ATA_PIO_MAX); } static device_method_t ata_cbus_methods[] = { /* device_interface */ DEVMETHOD(device_probe, ata_cbus_probe), DEVMETHOD(device_attach, ata_cbus_attach), /* bus methods */ DEVMETHOD(bus_alloc_resource, ata_cbus_alloc_resource), DEVMETHOD(bus_setup_intr, ata_cbus_setup_intr), DEVMETHOD(bus_print_child, ata_cbus_print_child), { 0, 0 } }; static driver_t ata_cbus_driver = { "atacbus", ata_cbus_methods, sizeof(struct ata_cbus_controller), }; static devclass_t ata_cbus_devclass; DRIVER_MODULE(atacbus, isa, ata_cbus_driver, ata_cbus_devclass, 0, 0); static int ata_cbussub_probe(device_t dev) { struct ata_cbus_controller *ctlr = device_get_softc(device_get_parent(dev)); struct ata_channel *ch = device_get_softc(dev); device_t *children; int count, i; /* find channel number on this controller */ device_get_children(device_get_parent(dev), &children, &count); for (i = 0; i < count; i++) { if (children[i] == dev) ch->unit = i; } free(children, M_TEMP); ch->flags |= ATA_USE_16BIT | ATA_USE_PC98GEOM; ch->device[MASTER].setmode = ctlr->setmode; ch->device[SLAVE].setmode = ctlr->setmode; ch->locking = ctlr->locking; return ata_probe(dev); } static device_method_t ata_cbussub_methods[] = { /* device interface */ DEVMETHOD(device_probe, ata_cbussub_probe), DEVMETHOD(device_attach, ata_attach), DEVMETHOD(device_detach, ata_detach), DEVMETHOD(device_resume, ata_resume), { 0, 0 } }; static driver_t ata_cbussub_driver = { "ata", ata_cbussub_methods, sizeof(struct ata_channel), }; DRIVER_MODULE(ata, atacbus, ata_cbussub_driver, ata_devclass, 0, 0);