diff --git a/sys/pccard/pccard.c b/sys/pccard/pccard.c index dd76c247be13..7d416a946f47 100644 --- a/sys/pccard/pccard.c +++ b/sys/pccard/pccard.c @@ -1,1112 +1,1113 @@ /* * pccard.c - Interface code for PC-CARD controllers. * * June 1995, Andrew McRae (andrew@mega.com.au) *------------------------------------------------------------------------- * * Copyright (c) 1995 Andrew McRae. 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. * - * $Id: pccard.c,v 1.71 1999/02/13 11:31:59 kuriyama Exp $ + * $Id: pccard.c,v 1.72 1999/02/14 20:41:01 guido Exp $ */ #include "opt_devfs.h" #include "opt_pcic.h" #include #include #include #include #include #include #include #include #ifdef DEVFS #include #endif /*DEVFS*/ #include #include #include #include #include #include #include "apm.h" #if NAPM > 0 #include #endif /* NAPM > 0 */ #include #include +#include #include #include /* * XXX We shouldn't be using processor-specific/bus-specific code in * here, but we need the start of the ISA hole (IOM_BEGIN). */ #ifdef PC98 #include #else #include #endif SYSCTL_NODE(_machdep, OID_AUTO, pccard, CTLFLAG_RW, 0, "pccard"); static int pcic_resume_reset = #ifdef PCIC_RESUME_RESET /* opt_pcic.h */ 1; #else 0; #endif SYSCTL_INT(_machdep_pccard, OID_AUTO, pcic_resume_reset, CTLFLAG_RW, &pcic_resume_reset, 0, ""); #define PCCARD_MEMSIZE (4*1024) #define MIN(a,b) ((a)<(b)?(a):(b)) static int allocate_driver(struct slot *, struct dev_desc *); static void inserted(void *); static void unregister_device_interrupt(struct pccard_devinfo *); static void disable_slot(struct slot *); static int invalid_io_memory(unsigned long, int); static struct pccard_device *find_driver(char *); static void remove_device(struct pccard_devinfo *); static inthand2_t slot_irq_handler; static void power_off_slot(void *); static void pccard_configure(void *); SYSINIT(pccard, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE + 1, pccard_configure, NULL); #if NAPM > 0 /* * For the APM stuff, the apmhook structure is kept * separate from the slot structure so that the slot * drivers do not need to know about the hooks (or the * data structures). */ static int slot_suspend(void *arg); static int slot_resume(void *arg); static struct apmhook s_hook[MAXSLOT]; /* APM suspend */ static struct apmhook r_hook[MAXSLOT]; /* APM resume */ #endif /* NAPM > 0 */ static struct slot *pccard_slots[MAXSLOT]; /* slot entries */ static struct slot *slot_list; static struct slot_ctrl *cont_list; static struct pccard_device *drivers; /* Card drivers */ /* * The driver interface for read/write uses a block * of memory in the ISA I/O memory space allocated via * an ioctl setting. */ static unsigned long pccard_mem; /* Physical memory */ static unsigned char *pccard_kmem; /* Kernel virtual address */ static d_open_t crdopen; static d_close_t crdclose; static d_read_t crdread; static d_write_t crdwrite; static d_ioctl_t crdioctl; static d_poll_t crdpoll; #define CDEV_MAJOR 50 static struct cdevsw crd_cdevsw = { crdopen, crdclose, crdread, crdwrite, /*50*/ crdioctl, nostop, nullreset, nodevtotty,/* pcmcia */ crdpoll, nommap, NULL, "crd", NULL, -1 }; /* * pccard_configure - called by autoconf code. * Probes for various PC-CARD controllers, and * initialises data structures to point to the * various slots. * * Each controller indicates the number of slots * that it sees, and these are mapped to a master * slot number accessed via the character device entries. * * XXX this is a relic. Each controller has it's own probe * configuration hook. Printing a list of configured devices * with pccard support probably isn't all that useful. */ static void pccard_configure(dummy) void *dummy; { struct pccard_device **driver, *drv; /* This isn't strictly correct, but works because of initialize order */ driver = &drivers; printf("Initializing PC-card drivers:"); while ((drv = *driver++)) printf(" %s", drv->name); printf("\n"); } int pccard_module_handler(module_t mod, int what, void *arg) { struct pccard_device *drv = (struct pccard_device *)arg; switch(what) { case MOD_LOAD: pccard_add_driver(drv); break; case MOD_UNLOAD: pccard_remove_driver(drv); break; default: break; } return 0; } /* * pccard_add_driver - Add a new driver to the list of * drivers available for allocation. */ void pccard_add_driver(struct pccard_device *drv) { /* * If already loaded, then reject the driver. */ if (find_driver(drv->name)) { printf("Driver %s already loaded\n", drv->name); return; } drv->next = drivers; drivers = drv; } /* * pccard_remove_driver - called to unlink driver * from devices. Usually called when drivers are * unloaded from kernel. */ void pccard_remove_driver(struct pccard_device *drv) { struct slot *slt; struct pccard_devinfo *devi, *next; struct pccard_device *drvlist; for (slt = slot_list; slt; slt = slt->next) for (devi = slt->devices; devi; devi = next) { next = devi->next; if (devi->drv == drv) remove_device(devi); } /* * Once all the devices belonging to this driver have been * freed, then remove the driver from the list * of registered drivers. */ if (drivers == drv) drivers = drv->next; else for (drvlist = drivers; drvlist->next; drvlist = drvlist->next) if (drvlist->next == drv) { drvlist->next = drv->next; break; } } /* * pccard_remove_controller - Called when the slot * driver is unloaded. The plan is to unload * drivers from the slots, and then remove the * slots from the slot list, and then finally * remove the controller structure. Messy... */ void pccard_remove_controller(struct slot_ctrl *ctrl) { struct slot *slt, *next, *last = 0; struct slot_ctrl *cl; struct pccard_devinfo *devi; for (slt = slot_list; slt; slt = next) { next = slt->next; /* * If this slot belongs to this controller, * remove this slot. */ if (slt->ctrl == ctrl) { pccard_slots[slt->slotnum] = 0; if (slt->insert_seq) untimeout(inserted, (void *)slt, slt->insert_ch); /* * Unload the drivers attached to this slot. */ while ((devi = slt->devices) != NULL) remove_device(devi); /* * Disable the slot and unlink the slot from the * slot list. */ disable_slot(slt); if (last) last->next = next; else slot_list = next; #if NAPM > 0 apm_hook_disestablish(APM_HOOK_SUSPEND, &s_hook[slt->slotnum]); apm_hook_disestablish(APM_HOOK_RESUME, &r_hook[slt->slotnum]); #endif if (ctrl->extra && slt->cdata) FREE(slt->cdata, M_DEVBUF); FREE(slt, M_DEVBUF); /* * Can't use slot after we have freed it. */ } else { last = slt; } } /* * Unlink controller structure from controller list. */ if (cont_list == ctrl) cont_list = ctrl->next; else for (cl = cont_list; cl->next; cl = cl->next) if (cl->next == ctrl) { cl->next = ctrl->next; break; } } /* * Power off the slot. * (doing it immediately makes the removal of some cards unstable) */ static void power_off_slot(void *arg) { struct slot *slt = (struct slot *)arg; /* Power off the slot. */ slt->pwr_off_pending = 0; slt->ctrl->disable(slt); } /* * unregister_device_interrupt - Disable the interrupt generation to * the device driver which is handling it, so we can remove it. */ static void unregister_device_interrupt(struct pccard_devinfo *devi) { struct slot *slt = devi->slt; int s; if (devi->running) { s = splhigh(); devi->drv->disable(devi); devi->running = 0; if (devi->isahd.id_irq && --slt->irqref <= 0) { printf("Return IRQ=%d\n",slt->irq); slt->ctrl->mapirq(slt, 0); INTRDIS(1<irq); - unregister_intr(slt->irq, slot_irq_handler); + unregister_pcic_intr(slt->irq, slot_irq_handler); if (devi->drv->imask) INTRUNMASK(*devi->drv->imask,(1<irq)); /* Remove from the PCIC controller imask */ if (slt->ctrl->imask) INTRUNMASK(*(slt->ctrl->imask), (1<irq)); slt->irq = 0; } splx(s); } } /* * disable_slot - Disables the slot by removing * the power and unmapping the I/O */ static void disable_slot(struct slot *slt) { struct pccard_devinfo *devi; int i; /* * Unload all the drivers on this slot. Note we can't * remove the device structures themselves, because this * may be called from the event routine, which is called * from the slot controller's ISR, and removing the structures * shouldn't happen during the middle of some driver activity. * * Note that a race condition is possible here; if a * driver is accessing the device and it is removed, then * all bets are off... */ for (devi = slt->devices; devi; devi = devi->next) unregister_device_interrupt(devi); /* Power off the slot 1/2 second after removal of the card */ slt->poff_ch = timeout(power_off_slot, (caddr_t)slt, hz / 2); slt->pwr_off_pending = 1; /* De-activate all contexts. */ for (i = 0; i < slt->ctrl->maxmem; i++) if (slt->mem[i].flags & MDF_ACTIVE) { slt->mem[i].flags = 0; (void)slt->ctrl->mapmem(slt, i); } for (i = 0; i < slt->ctrl->maxio; i++) if (slt->io[i].flags & IODF_ACTIVE) { slt->io[i].flags = 0; (void)slt->ctrl->mapio(slt, i); } } /* * APM hooks for suspending and resuming. */ #if NAPM > 0 static int slot_suspend(void *arg) { struct slot *slt = arg; /* This code stolen from pccard_event:card_removed */ if (slt->state == filled) { int s = splhigh(); disable_slot(slt); slt->laststate = filled; slt->state = suspend; splx(s); printf("Card disabled, slot %d\n", slt->slotnum); } /* * Disable any pending timeouts for this slot since we're * powering it down/disabling now. */ untimeout(power_off_slot, (caddr_t)slt, slt->poff_ch); slt->ctrl->disable(slt); return (0); } static int slot_resume(void *arg) { struct slot *slt = arg; if (pcic_resume_reset) slt->ctrl->resume(slt); /* This code stolen from pccard_event:card_inserted */ if (slt->state == suspend) { slt->laststate = suspend; slt->state = empty; slt->insert_seq = 1; untimeout(inserted, (void *)slt, slt->insert_ch); slt->insert_ch = timeout(inserted, (void *)slt, hz/4); selwakeup(&slt->selp); } return (0); } #endif /* NAPM > 0 */ /* * pccard_alloc_slot - Called from controller probe * routine, this function allocates a new PC-CARD slot * and initialises the data structures using the data provided. * It returns the allocated structure to the probe routine * to allow the controller specific data to be initialised. */ struct slot * pccard_alloc_slot(struct slot_ctrl *ctrl) { struct slot *slt; int slotno; for (slotno = 0; slotno < MAXSLOT; slotno++) if (pccard_slots[slotno] == 0) break; if (slotno == MAXSLOT) return(0); MALLOC(slt, struct slot *, sizeof(*slt), M_DEVBUF, M_WAITOK); bzero(slt, sizeof(*slt)); #ifdef DEVFS slt->devfs_token = devfs_add_devswf(&crd_cdevsw, slotno, DV_CHR, 0, 0, 0600, "card%d", slotno); #endif if (ctrl->extra) { MALLOC(slt->cdata, void *, ctrl->extra, M_DEVBUF, M_WAITOK); bzero(slt->cdata, ctrl->extra); } slt->ctrl = ctrl; slt->slotnum = slotno; pccard_slots[slotno] = slt; slt->next = slot_list; slot_list = slt; /* * If this controller hasn't been seen before, then * link it into the list of controllers. */ if (ctrl->slots++ == 0) { ctrl->next = cont_list; cont_list = ctrl; if (ctrl->maxmem > NUM_MEM_WINDOWS) ctrl->maxmem = NUM_MEM_WINDOWS; if (ctrl->maxio > NUM_IO_WINDOWS) ctrl->maxio = NUM_IO_WINDOWS; printf("PC-Card %s (%d mem & %d I/O windows)\n", ctrl->name, ctrl->maxmem, ctrl->maxio); } callout_handle_init(&slt->insert_ch); callout_handle_init(&slt->poff_ch); #if NAPM > 0 { struct apmhook *ap; ap = &s_hook[slt->slotnum]; ap->ah_fun = slot_suspend; ap->ah_arg = (void *)slt; ap->ah_name = ctrl->name; ap->ah_order = APM_MID_ORDER; apm_hook_establish(APM_HOOK_SUSPEND, ap); ap = &r_hook[slt->slotnum]; ap->ah_fun = slot_resume; ap->ah_arg = (void *)slt; ap->ah_name = ctrl->name; ap->ah_order = APM_MID_ORDER; apm_hook_establish(APM_HOOK_RESUME, ap); } #endif /* NAPM > 0 */ return(slt); } /* * pccard_alloc_intr - allocate an interrupt from the * free interrupts and return its number. The interrupts * allowed are passed as a mask. */ int pccard_alloc_intr(u_int imask, inthand2_t *hand, int unit, u_int *maskp, u_int *pcic_imask) { int irq; unsigned int mask; for (irq = 1; irq < ICU_LEN; irq++) { mask = 1ul << irq; if (!(mask & imask)) continue; INTRMASK(*maskp, mask); - if (register_intr(irq, 0, 0, hand, maskp, unit) == 0) { + if (register_pcic_intr(irq, 0, 0, hand, maskp, unit) == 0) { /* add this to the PCIC controller's mask */ if (pcic_imask) INTRMASK(*pcic_imask, (1 << irq)); update_intr_masks(); INTREN(mask); return(irq); } /* No luck, remove from mask again... */ INTRUNMASK(*maskp, mask); update_intr_masks(); } return(-1); } /* * allocate_driver - Create a new device entry for this * slot, and attach a driver to it. */ static int allocate_driver(struct slot *slt, struct dev_desc *desc) { struct pccard_devinfo *devi; struct pccard_device *drv; int err, irq = 0, s; drv = find_driver(desc->name); if (drv == 0) return(ENXIO); /* * If an instance of this driver is already installed, * but not running, then remove it. If it is running, * then reject the request. */ for (devi = slt->devices; devi; devi = devi->next) if (devi->drv == drv && devi->isahd.id_unit == desc->unit) { if (devi->running) return(EBUSY); remove_device(devi); break; } /* * If an interrupt mask has been given, then check it * against the slot interrupt (if one has been allocated). */ if (desc->irqmask && drv->imask) { if ((slt->ctrl->irqs & desc->irqmask) == 0) return(EINVAL); if (slt->irq) { if (((1 << slt->irq) & desc->irqmask) == 0) return(EINVAL); slt->irqref++; irq = slt->irq; } else { /* * Attempt to allocate an interrupt. * XXX We lose at the moment if the second * device relies on a different interrupt mask. */ irq = pccard_alloc_intr(desc->irqmask, slot_irq_handler, (int)slt, drv->imask, slt->ctrl->imask); if (irq < 0) return(EINVAL); slt->irq = irq; slt->irqref = 1; slt->ctrl->mapirq(slt, slt->irq); } } MALLOC(devi, struct pccard_devinfo *, sizeof(*devi), M_DEVBUF, M_WAITOK); bzero(devi, sizeof(*devi)); /* * Create an entry for the device under this slot. */ devi->running = 1; devi->drv = drv; devi->slt = slt; devi->isahd.id_irq = irq; devi->isahd.id_unit = desc->unit; devi->isahd.id_msize = desc->memsize; devi->isahd.id_iobase = desc->iobase; bcopy(desc->misc, devi->misc, sizeof(desc->misc)); if (irq) devi->isahd.id_irq = 1 << irq; devi->isahd.id_flags = desc->flags; /* * Convert the memory to kernel space. */ if (desc->mem) devi->isahd.id_maddr = (caddr_t)(void *)(uintptr_t) (desc->mem + atdevbase - IOM_BEGIN); else devi->isahd.id_maddr = 0; devi->next = slt->devices; slt->devices = devi; s = splhigh(); err = drv->enable(devi); splx(s); /* * If the enable functions returns no error, then the * device has been successfully installed. If so, then * attach it to the slot, otherwise free it and return * the error. We assume that when we free the device, * it will also set 'running' to off. */ if (err) remove_device(devi); return(err); } static void remove_device(struct pccard_devinfo *devi) { struct slot *slt = devi->slt; struct pccard_devinfo *list; /* * If an interrupt is enabled on this slot, * then unregister it if no-one else is using it. */ unregister_device_interrupt(devi); /* * Remove from device list on this slot. */ if (slt->devices == devi) slt->devices = devi->next; else for (list = slt->devices; list->next; list = list->next) if (list->next == devi) { list->next = devi->next; break; } /* * Finally, free the memory space. */ FREE(devi, M_DEVBUF); } /* * card insert routine - Called from a timeout to debounce * insertion events. */ static void inserted(void *arg) { struct slot *slt = arg; slt->state = filled; /* * Enable 5V to the card so that the CIS can be read. */ slt->pwr.vcc = 50; slt->pwr.vpp = 0; /* * Disable any pending timeouts for this slot, and explicitly * power it off right now. Then, re-enable the power using * the (possibly new) power settings. */ untimeout(power_off_slot, (caddr_t)slt, slt->poff_ch); power_off_slot(slt); slt->ctrl->power(slt); printf("Card inserted, slot %d\n", slt->slotnum); /* * Now start resetting the card. */ slt->ctrl->reset(slt); } /* * Card event callback. Called at splhigh to prevent * device interrupts from interceding. */ void pccard_event(struct slot *slt, enum card_event event) { if (slt->insert_seq) { slt->insert_seq = 0; untimeout(inserted, (void *)slt, slt->insert_ch); } switch(event) { case card_removed: /* * The slot and devices are disabled, but the * data structures are not unlinked. */ if (slt->state == filled) { int s = splhigh(); disable_slot(slt); slt->state = empty; splx(s); printf("Card removed, slot %d\n", slt->slotnum); pccard_remove_beep(); selwakeup(&slt->selp); } break; case card_inserted: slt->insert_seq = 1; slt->insert_ch = timeout(inserted, (void *)slt, hz/4); pccard_remove_beep(); break; } } /* * slot_irq_handler - Interrupt handler for shared irq devices. */ static void slot_irq_handler(void *arg) { struct pccard_devinfo *devi; struct slot *slt = (struct slot *)arg; /* * For each device that has the shared interrupt, * call the interrupt handler. If the interrupt was * caught, the handler returns true. */ for (devi = slt->devices; devi; devi = devi->next) if (devi->isahd.id_irq && devi->running && devi->drv->handler(devi)) return; /* * XXX - Should 'debounce' these for drivers that have recently * been removed. */ printf("Slot %d, unfielded interrupt (%d)\n", slt->slotnum, slt->irq); } /* * Device driver interface. */ static int crdopen(dev_t dev, int oflags, int devtype, struct proc *p) { struct slot *slt; if (minor(dev) >= MAXSLOT) return(ENXIO); slt = pccard_slots[minor(dev)]; if (slt == 0) return(ENXIO); if (slt->rwmem == 0) slt->rwmem = MDF_ATTR; return(0); } /* * Close doesn't de-allocate any resources, since * slots may be assigned to drivers already. */ static int crdclose(dev_t dev, int fflag, int devtype, struct proc *p) { return(0); } /* * read interface. Map memory at lseek offset, * then transfer to user space. */ static int crdread(dev_t dev, struct uio *uio, int ioflag) { struct slot *slt = pccard_slots[minor(dev)]; struct mem_desc *mp, oldmap; unsigned char *p; unsigned int offs; int error = 0, win, count; if (slt == 0 || slt->state != filled) return(ENXIO); if (pccard_mem == 0) return(ENOMEM); for (win = 0; win < slt->ctrl->maxmem; win++) if ((slt->mem[win].flags & MDF_ACTIVE) == 0) break; if (win >= slt->ctrl->maxmem) return(EBUSY); mp = &slt->mem[win]; oldmap = *mp; mp->flags = slt->rwmem|MDF_ACTIVE; #if 0 printf("Rd at offs %d, size %d\n", (int)uio->uio_offset, uio->uio_resid); #endif while (uio->uio_resid && error == 0) { mp->card = uio->uio_offset; mp->size = PCCARD_MEMSIZE; mp->start = (caddr_t)(void *)(uintptr_t)pccard_mem; if ((error = slt->ctrl->mapmem(slt, win)) != 0) break; offs = (unsigned int)uio->uio_offset & (PCCARD_MEMSIZE - 1); p = pccard_kmem + offs; count = MIN(PCCARD_MEMSIZE - offs, uio->uio_resid); error = uiomove(p, count, uio); } /* * Restore original map. */ *mp = oldmap; slt->ctrl->mapmem(slt, win); return(error); } /* * crdwrite - Write data to card memory. * Handles wrap around so that only one memory * window is used. */ static int crdwrite(dev_t dev, struct uio *uio, int ioflag) { struct slot *slt = pccard_slots[minor(dev)]; struct mem_desc *mp, oldmap; unsigned char *p; unsigned int offs; int error = 0, win, count; if (slt == 0 || slt->state != filled) return(ENXIO); if (pccard_mem == 0) return(ENOMEM); for (win = 0; win < slt->ctrl->maxmem; win++) if ((slt->mem[win].flags & MDF_ACTIVE)==0) break; if (win >= slt->ctrl->maxmem) return(EBUSY); mp = &slt->mem[win]; oldmap = *mp; mp->flags = slt->rwmem|MDF_ACTIVE; #if 0 printf("Wr at offs %d, size %d\n", (int)uio->uio_offset, uio->uio_resid); #endif while (uio->uio_resid && error == 0) { mp->card = uio->uio_offset; mp->size = PCCARD_MEMSIZE; mp->start = (caddr_t)(void *)(uintptr_t)pccard_mem; if ((error = slt->ctrl->mapmem(slt, win)) != 0) break; offs = (unsigned int)uio->uio_offset & (PCCARD_MEMSIZE - 1); p = pccard_kmem + offs; count = MIN(PCCARD_MEMSIZE - offs, uio->uio_resid); #if 0 printf("Writing %d bytes to address 0x%x\n", count, p); #endif error = uiomove(p, count, uio); } /* * Restore original map. */ *mp = oldmap; slt->ctrl->mapmem(slt, win); return(error); } /* * ioctl calls - allows setting/getting of memory and I/O * descriptors, and assignment of drivers. */ static int crdioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p) { struct slot *slt = pccard_slots[minor(dev)]; struct mem_desc *mp; struct io_desc *ip; int s, err; /* beep is disabled until the 1st call of crdioctl() */ pccard_beep_select(BEEP_ON); if (slt == 0 && cmd != PIOCRWMEM) return(ENXIO); switch(cmd) { default: if (slt->ctrl->ioctl) return(slt->ctrl->ioctl(slt, cmd, data)); return(EINVAL); /* * Get slot state. */ case PIOCGSTATE: s = splhigh(); ((struct slotstate *)data)->state = slt->state; ((struct slotstate *)data)->laststate = slt->laststate; slt->laststate = slt->state; splx(s); ((struct slotstate *)data)->maxmem = slt->ctrl->maxmem; ((struct slotstate *)data)->maxio = slt->ctrl->maxio; ((struct slotstate *)data)->irqs = slt->ctrl->irqs; break; /* * Get memory context. */ case PIOCGMEM: s = ((struct mem_desc *)data)->window; if (s < 0 || s >= slt->ctrl->maxmem) return(EINVAL); mp = &slt->mem[s]; ((struct mem_desc *)data)->flags = mp->flags; ((struct mem_desc *)data)->start = mp->start; ((struct mem_desc *)data)->size = mp->size; ((struct mem_desc *)data)->card = mp->card; break; /* * Set memory context. If context already active, then unmap it. * It is hard to see how the parameters can be checked. * At the very least, we only allow root to set the context. */ case PIOCSMEM: if (suser(p->p_ucred, &p->p_acflag)) return(EPERM); if (slt->state != filled) return(ENXIO); s = ((struct mem_desc *)data)->window; if (s < 0 || s >= slt->ctrl->maxmem) return(EINVAL); slt->mem[s] = *((struct mem_desc *)data); return(slt->ctrl->mapmem(slt, s)); /* * Get I/O port context. */ case PIOCGIO: s = ((struct io_desc *)data)->window; if (s < 0 || s >= slt->ctrl->maxio) return(EINVAL); ip = &slt->io[s]; ((struct io_desc *)data)->flags = ip->flags; ((struct io_desc *)data)->start = ip->start; ((struct io_desc *)data)->size = ip->size; break; /* * Set I/O port context. */ case PIOCSIO: if (suser(p->p_ucred, &p->p_acflag)) return(EPERM); if (slt->state != filled) return(ENXIO); s = ((struct io_desc *)data)->window; if (s < 0 || s >= slt->ctrl->maxio) return(EINVAL); slt->io[s] = *((struct io_desc *)data); return(slt->ctrl->mapio(slt, s)); break; /* * Set memory window flags for read/write interface. */ case PIOCRWFLAG: slt->rwmem = *(int *)data; break; /* * Set the memory window to be used for the read/write interface. */ case PIOCRWMEM: if (*(unsigned long *)data == 0) { if (pccard_mem) *(unsigned long *)data = pccard_mem; break; } if (suser(p->p_ucred, &p->p_acflag)) return(EPERM); /* * Validate the memory by checking it against the I/O * memory range. It must also start on an aligned block size. */ if (invalid_io_memory(*(unsigned long *)data, PCCARD_MEMSIZE)) return(EINVAL); if (*(unsigned long *)data & (PCCARD_MEMSIZE-1)) return(EINVAL); /* * Map it to kernel VM. */ pccard_mem = *(unsigned long *)data; pccard_kmem = (unsigned char *)(void *)(uintptr_t) (pccard_mem + atdevbase - IOM_BEGIN); break; /* * Set power values. */ case PIOCSPOW: slt->pwr = *(struct power *)data; return(slt->ctrl->power(slt)); /* * Allocate a driver to this slot. */ case PIOCSDRV: if (suser(p->p_ucred, &p->p_acflag)) return(EPERM); err = allocate_driver(slt, (struct dev_desc *)data); if (!err) pccard_success_beep(); else pccard_failure_beep(); return err; case PIOCSBEEP: if (pccard_beep_select(*(int *)data)) { return EINVAL; } break; } return(0); } /* * poll - Poll on exceptions will return true * when a change in card status occurs. */ static int crdpoll(dev_t dev, int events, struct proc *p) { int s; struct slot *slt = pccard_slots[minor(dev)]; int revents = 0; if (events & (POLLIN | POLLRDNORM)) revents |= events & (POLLIN | POLLRDNORM); if (events & (POLLOUT | POLLWRNORM)) revents |= events & (POLLIN | POLLRDNORM); s = splhigh(); /* * select for exception - card event. */ if (events & POLLRDBAND) if (slt == 0 || slt->laststate != slt->state) revents |= POLLRDBAND; if (revents == 0) selrecord(p, &slt->selp); splx(s); return (revents); } /* * invalid_io_memory - verify that the ISA I/O memory block * is a valid and unallocated address. * A simple check of the range is done, and then a * search of the current devices is done to check for * overlapping regions. */ static int invalid_io_memory(unsigned long adr, int size) { /* XXX - What's magic about 0xC0000?? */ if (adr < 0xC0000 || (adr+size) > IOM_END) return(1); return(0); } static struct pccard_device * find_driver(char *name) { struct pccard_device *drv; for (drv = drivers; drv; drv = drv->next) if (strcmp(drv->name, name)==0) return(drv); return(0); } static crd_devsw_installed = 0; static void crd_drvinit(void *unused) { dev_t dev; if (!crd_devsw_installed) { dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev, &crd_cdevsw, NULL); crd_devsw_installed = 1; } } SYSINIT(crddev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,crd_drvinit,NULL) diff --git a/sys/pccard/pcic.c b/sys/pccard/pcic.c index 651c1d5b4bc8..c087239b8e36 100644 --- a/sys/pccard/pcic.c +++ b/sys/pccard/pcic.c @@ -1,1132 +1,1172 @@ /* * Intel PCIC or compatible Controller driver * May be built to make a loadable module. *------------------------------------------------------------------------- * * Copyright (c) 1995 Andrew McRae. 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. */ /* * pcic98 : PC9801 original PCMCIA controller code for NS/A,Ne,NX/C,NR/L. * by Noriyuki Hosobuchi */ #include #include #include #include #include #include #include #include #include #include #ifdef PC98 #include #endif #include #include #include +#include + +#ifdef APIC_IO +#include +#endif /* * Prototypes for interrupt handler. */ static inthand2_t pcicintr; static int pcic_ioctl __P((struct slot *, int, caddr_t)); static int pcic_power __P((struct slot *)); static timeout_t pcic_reset; static void pcic_resume(struct slot *); static void pcic_disable __P((struct slot *)); static void pcic_mapirq __P((struct slot *, int)); static timeout_t pcictimeout; static struct callout_handle pcictimeout_ch = CALLOUT_HANDLE_INITIALIZER(&pcictimeout_ch); static int pcic_modevent __P((module_t, int, void *)); static int pcic_unload __P((void)); static int pcic_memory(struct slot *, int); static int pcic_io(struct slot *, int); static u_int build_freelist(u_int); /* * Per-slot data table. */ static struct pcic_slot { int slotnum; /* My slot number */ int index; /* Index register */ int data; /* Data register */ int offset; /* Offset value for index */ char controller; /* Device type */ char revision; /* Device Revision */ struct slot *slt; /* Back ptr to slot */ u_char (*getb)(struct pcic_slot *, int); void (*putb)(struct pcic_slot *, int, u_char); u_char *regs; /* Pointer to regs in mem */ } pcic_slots[PCIC_MAX_SLOTS]; static int pcic_irq; static unsigned pcic_imask; static struct slot_ctrl cinfo; /* * Internal inline functions for accessing the PCIC. */ /* * Read a register from the PCIC. */ static __inline unsigned char getb1(struct pcic_slot *sp, int reg) { outb(sp->index, sp->offset + reg); return inb(sp->data); } static __inline unsigned char getb2(struct pcic_slot *sp, int reg) { return (sp->regs[reg]); } /* * Write a register on the PCIC */ static __inline void putb1(struct pcic_slot *sp, int reg, unsigned char val) { outb(sp->index, sp->offset + reg); outb(sp->data, val); } static __inline void putb2(struct pcic_slot *sp, int reg, unsigned char val) { sp->regs[reg] = val; } /* * Clear bit(s) of a register. */ static __inline void clrb(struct pcic_slot *sp, int reg, unsigned char mask) { sp->putb(sp, reg, sp->getb(sp, reg) & ~mask); } /* * Set bit(s) of a register */ static __inline void setb(struct pcic_slot *sp, int reg, unsigned char mask) { sp->putb(sp, reg, sp->getb(sp, reg) | mask); } /* * Write a 16 bit value to 2 adjacent PCIC registers */ static __inline void putw(struct pcic_slot *sp, int reg, unsigned short word) { sp->putb(sp, reg, word & 0xFF); sp->putb(sp, reg + 1, (word >> 8) & 0xff); } +/* + * Gerneral functions for registering and unregistering interrupts. + * isa_to_apic() is used to map the ISA IRQ onto the APIC IRQ to + * check if the APIC IRQ is used or free. + */ +#ifdef APIC_IO +int register_pcic_intr(int intr, int device_id, u_int flags, + inthand2_t handler, u_int *maskptr, int unit){ + int apic_intr; + apic_intr = isa_apic_irq(intr); + if (apic_intr <0) return -1; + else return register_intr(apic_intr, device_id, flags, handler, + maskptr, unit); +} + +int unregister_pcic_intr(int intr, inthand2_t handler){ + int apic_intr; + apic_intr = isa_apic_irq(intr); + return unregister_intr(apic_intr, handler); +} + +#else /* Not APIC_IO */ + +static int register_pcic_intr(int intr, int device_id, u_int flags, + inthand2_t handler, u_int *maskptr, int unit){ + return register_intr(intr, device_id, flags, handler, maskptr, unit); +} + +static int unregister_pcic_intr(int intr, inthand2_t handler){ + return unregister_intr(intr, handler); +} + +#endif /* APIC_IO */ + + /* * Loadable kernel module interface. */ /* * Module handler that processes loads and unloads. * Once the module is loaded, the probe routine * is called to install the slots (if any). */ static int pcic_modevent(module_t mod, int what, void *arg) { int err = 0; /* default = success*/ static int pcic_started = 0; switch (what) { case MOD_LOAD: /* * Call the probe routine to find the slots. If * no slots exist, then don't bother loading the module. * XXX but this is not appropriate as a static module. */ if (pcic_probe()) pcic_started = 1; break; case MOD_UNLOAD: /* * Attempt to unload the slot driver. */ if (pcic_started) { printf("Unloading PCIC driver\n"); err = pcic_unload(); pcic_started = 0; } break; /* Success*/ default: /* we only care about load/unload; ignore shutdown */ break; } return(err); } static moduledata_t pcic_mod = { "pcic", pcic_modevent, 0 }; /* After configure() has run.. bring on the new bus system! */ DECLARE_MODULE(pcic, pcic_mod, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE); /* * pcic_unload - Called when unloading a LKM. * Disables interrupts and resets PCIC. */ static int pcic_unload() { int slot; struct pcic_slot *sp = pcic_slots; untimeout(pcictimeout, 0, pcictimeout_ch); if (pcic_irq) { for (slot = 0; slot < PCIC_MAX_SLOTS; slot++, sp++) { if (sp->slt) sp->putb(sp, PCIC_STAT_INT, 0); } - unregister_intr(pcic_irq, pcicintr); + unregister_pcic_intr(pcic_irq, pcicintr); } pccard_remove_controller(&cinfo); return(0); } #if 0 static void pcic_dump_attributes(unsigned char *scratch, int maxlen) { int i,j,k; i = 0; while (scratch[i] != 0xff && i < maxlen) { unsigned char link = scratch[i+2]; /* * Dump attribute memory */ if (scratch[i]) { printf("[%02x] ", i); for (j = 0; j < 2 * link + 4 && j < 128; j += 2) printf("%02x ", scratch[j + i]); printf("\n"); } i += 4 + 2 * link; } } #endif static void nullfunc(void *unused) { /* empty */ } static u_int build_freelist(u_int pcic_mask) { int irq; u_int mask, freemask; /* No free IRQs (yet). */ freemask = 0; /* Walk through all of the IRQ's and find any that aren't allocated. */ for (irq = 1; irq < ICU_LEN; irq++) { /* * If the PCIC controller can't generate it, don't * bother checking to see if it it's free. */ mask = 1 << irq; if (!(mask & pcic_mask)) continue; - + /* See if the IRQ is free. */ - if (register_intr(irq, 0, 0, nullfunc, NULL, irq) == 0) { + if (register_pcic_intr(irq, 0, 0, nullfunc, NULL, irq) == 0) { /* Give it back, but add it to the mask */ INTRMASK(freemask, mask); - unregister_intr(irq, nullfunc); + unregister_pcic_intr(irq, nullfunc); } } #ifdef PCIC_DEBUG printf("Freelist of IRQ's <0x%x>\n", freemask); #endif return freemask; } /* * entry point from main code to map/unmap memory context. */ static int pcic_memory(struct slot *slt, int win) { struct pcic_slot *sp = slt->cdata; struct mem_desc *mp = &slt->mem[win]; int reg = mp->window * PCIC_MEMSIZE + PCIC_MEMBASE; #ifdef PC98 if (sp->controller == PCIC_PC98) { if (mp->flags & MDF_ACTIVE) { /* slot = 0, window = 0, sys_addr = 0xda000, length = 8KB */ unsigned char x; if ((unsigned long)mp->start != 0xda000) { printf("sys_addr must be 0xda000. requested address = 0x%x\n", mp->start); return(EINVAL); } /* omajinai ??? */ outb(PCIC98_REG0, 0); x = inb(PCIC98_REG1); x &= 0xfc; x |= 0x02; outb(PCIC98_REG1, x); outw(PCIC98_REG_PAGOFS, 0); if (mp->flags & MDF_ATTR) { outb(PCIC98_REG6, inb(PCIC98_REG6) | PCIC98_ATTRMEM); }else{ outb(PCIC98_REG6, inb(PCIC98_REG6) & (~PCIC98_ATTRMEM)); } outb(PCIC98_REG_WINSEL, PCIC98_MAPWIN); #if 0 if (mp->flags & MDF_16BITS == 1) { /* 16bit */ outb(PCIC98_REG2, inb(PCIC98_REG2) & (~PCIC98_8BIT)); }else{ /* 8bit */ outb(PCIC98_REG2, inb(PCIC98_REG2) | PCIC98_8BIT); } #endif }else{ outb(PCIC98_REG_WINSEL, PCIC98_UNMAPWIN); } return 0; } #endif /* PC98 */ if (mp->flags & MDF_ACTIVE) { unsigned long sys_addr = (uintptr_t)(void *)mp->start >> 12; /* * Write the addresses, card offsets and length. * The values are all stored as the upper 12 bits of the * 24 bit address i.e everything is allocated as 4 Kb chunks. */ putw(sp, reg, sys_addr & 0xFFF); putw(sp, reg+2, (sys_addr + (mp->size >> 12) - 1) & 0xFFF); putw(sp, reg+4, ((mp->card >> 12) - sys_addr) & 0x3FFF); #if 0 printf("card offs = card_adr = 0x%x 0x%x, sys_addr = 0x%x\n", mp->card, ((mp->card >> 12) - sys_addr) & 0x3FFF, sys_addr); #endif /* * Each 16 bit register has some flags in the upper bits. */ if (mp->flags & MDF_16BITS) setb(sp, reg+1, PCIC_DATA16); if (mp->flags & MDF_ZEROWS) setb(sp, reg+1, PCIC_ZEROWS); if (mp->flags & MDF_WS0) setb(sp, reg+3, PCIC_MW0); if (mp->flags & MDF_WS1) setb(sp, reg+3, PCIC_MW1); if (mp->flags & MDF_ATTR) setb(sp, reg+5, PCIC_REG); if (mp->flags & MDF_WP) setb(sp, reg+5, PCIC_WP); #if 0 printf("Slot number %d, reg 0x%x, offs 0x%x\n", sp->slotnum, reg, sp->offset); printf("Map window to sys addr 0x%x for %d bytes, card 0x%x\n", mp->start, mp->size, mp->card); printf("regs are: 0x%02x%02x 0x%02x%02x 0x%02x%02x flags 0x%x\n", sp->getb(sp, reg), sp->getb(sp, reg+1), sp->getb(sp, reg+2), sp->getb(sp, reg+3), sp->getb(sp, reg+4), sp->getb(sp, reg+5), mp->flags); #endif /* * Enable the memory window. By experiment, we need a delay. */ setb(sp, PCIC_ADDRWINE, (1<cdata; struct io_desc *ip = &slt->io[win]; #ifdef PC98 if (sp->controller == PCIC_PC98) { unsigned char x; #if 0 if (win =! 0) { printf("pcic98:Illegal PCIC I/O window request(%d)!", win); return(EINVAL); } #endif if (ip->flags & IODF_ACTIVE) { unsigned short base; x = inb(PCIC98_REG2) & 0x0f; if (! (ip->flags & IODF_16BIT)) x |= PCIC98_8BIT; if (ip->size > 16) /* 128bytes mapping */ x |= PCIC98_MAP128; x |= PCIC98_IOMEMORY; outb(PCIC98_REG2, x); base = 0x80d0; outw(PCIC98_REG4, base); /* 98side IO base */ outw(PCIC98_REG5, ip->start); /* card side IO base */ #ifdef PCIC_DEBUG printf("pcic98: IO mapped 0x%04x(98) -> 0x%04x(Card) and width %d bytes\n", base, ip->start, ip->size); #endif ip->start = base; }else{ outb(PCIC98_REG2, inb(PCIC98_REG2) & (~PCIC98_IOMEMORY)); } return 0; } #endif switch (win) { case 0: mask = PCIC_IO0_EN; reg = PCIC_IO0; break; case 1: mask = PCIC_IO1_EN; reg = PCIC_IO1; break; default: panic("Illegal PCIC I/O window request!"); } if (ip->flags & IODF_ACTIVE) { unsigned char x, ioctlv; #ifdef PCIC_DEBUG printf("Map I/O 0x%x (size 0x%x) on Window %d\n", ip->start, ip->size, win); #endif /* PCIC_DEBUG */ putw(sp, reg, ip->start); putw(sp, reg+2, ip->start+ip->size-1); x = 0; if (ip->flags & IODF_ZEROWS) x |= PCIC_IO_0WS; if (ip->flags & IODF_WS) x |= PCIC_IO_WS; if (ip->flags & IODF_CS16) x |= PCIC_IO_CS16; if (ip->flags & IODF_16BIT) x |= PCIC_IO_16BIT; /* * Extract the current flags and merge with new flags. * Flags for window 0 in lower nybble, and in upper nybble * for window 1. */ ioctlv = sp->getb(sp, PCIC_IOCTL); DELAY(100); switch (win) { case 0: sp->putb(sp, PCIC_IOCTL, x | (ioctlv & 0xf0)); break; case 1: sp->putb(sp, PCIC_IOCTL, (x << 4) | (ioctlv & 0xf)); break; } DELAY(100); setb(sp, PCIC_ADDRWINE, mask); DELAY(100); } else { clrb(sp, PCIC_ADDRWINE, mask); DELAY(100); putw(sp, reg, 0); putw(sp, reg + 2, 0); } return(0); } /* * Look for an Intel PCIC (or compatible). * For each available slot, allocate a PC-CARD slot. */ /* * VLSI 82C146 has incompatibilities about the I/O address * of slot 1. Assume it's the only PCIC whose vendor ID is 0x84, * contact Nate Williams if incorrect. */ int pcic_probe(void) { int slotnum, validslots = 0; u_int free_irqs, desired_irq; struct slot *slt; struct pcic_slot *sp; unsigned char c; static int maybe_vlsi = 0; /* Determine the list of free interrupts */ free_irqs = build_freelist(PCIC_INT_MASK_ALLOWED); /* * Initialise controller information structure. */ cinfo.mapmem = pcic_memory; cinfo.mapio = pcic_io; cinfo.ioctl = pcic_ioctl; cinfo.power = pcic_power; cinfo.mapirq = pcic_mapirq; cinfo.reset = pcic_reset; cinfo.disable = pcic_disable; cinfo.resume = pcic_resume; cinfo.maxmem = PCIC_MEM_WIN; cinfo.maxio = PCIC_IO_WIN; cinfo.irqs = free_irqs; cinfo.imask = &pcic_imask; sp = pcic_slots; for (slotnum = 0; slotnum < PCIC_MAX_SLOTS; slotnum++, sp++) { /* * Initialise the PCIC slot table. */ sp->getb = getb1; sp->putb = putb1; if (slotnum < 4) { sp->index = PCIC_INDEX_0; sp->data = PCIC_DATA_0; sp->offset = slotnum * PCIC_SLOT_SIZE; } else { sp->index = PCIC_INDEX_1; sp->data = PCIC_DATA_1; sp->offset = (slotnum - 4) * PCIC_SLOT_SIZE; } /* * XXX - Screwed up slot 1 on the VLSI chips. According to * the Linux PCMCIA code from David Hinds, working chipsets * return 0x84 from their (correct) ID ports, while the broken * ones would need to be probed at the new offset we set after * we assume it's broken. */ if (slotnum == 1 && maybe_vlsi && sp->getb(sp, PCIC_ID_REV) != 0x84) { sp->index += 4; sp->data += 4; sp->offset = PCIC_SLOT_SIZE << 1; } /* * see if there's a PCMCIA controller here * Intel PCMCIA controllers use 0x82 and 0x83 * IBM clone chips use 0x88 and 0x89, apparently */ c = sp->getb(sp, PCIC_ID_REV); sp->revision = -1; switch(c) { /* * 82365 or clones. */ case 0x82: case 0x83: sp->controller = PCIC_I82365; sp->revision = c & 1; /* * Now check for VADEM chips. */ outb(sp->index, 0x0E); outb(sp->index, 0x37); setb(sp, 0x3A, 0x40); c = sp->getb(sp, PCIC_ID_REV); if (c & 0x08) { switch (sp->revision = c & 7) { case 1: sp->controller = PCIC_VG365; break; case 2: sp->controller = PCIC_VG465; break; case 3: sp->controller = PCIC_VG468; break; default: sp->controller = PCIC_VG469; break; } clrb(sp, 0x3A, 0x40); } /* * Check for RICOH RF5C396 PCMCIA Controller */ c = sp->getb(sp, 0x3a); if (c == 0xb2) { sp->controller = PCIC_RF5C396; } break; /* * VLSI chips. */ case 0x84: sp->controller = PCIC_VLSI; maybe_vlsi = 1; break; case 0x88: case 0x89: sp->controller = PCIC_IBM; sp->revision = c & 1; break; case 0x8a: sp->controller = PCIC_IBM_KING; sp->revision = c & 1; break; default: continue; } /* * Check for Cirrus logic chips. */ sp->putb(sp, 0x1F, 0); c = sp->getb(sp, 0x1F); if ((c & 0xC0) == 0xC0) { c = sp->getb(sp, 0x1F); if ((c & 0xC0) == 0) { if (c & 0x20) sp->controller = PCIC_PD672X; else sp->controller = PCIC_PD6710; sp->revision = 8 - ((c & 0x1F) >> 2); } } switch(sp->controller) { case PCIC_I82365: cinfo.name = "Intel 82365"; break; case PCIC_IBM: cinfo.name = "IBM PCIC"; break; case PCIC_IBM_KING: cinfo.name = "IBM KING PCMCIA Controller"; break; case PCIC_PD672X: cinfo.name = "Cirrus Logic PD672X"; break; case PCIC_PD6710: cinfo.name = "Cirrus Logic PD6710"; break; case PCIC_VG365: cinfo.name = "Vadem 365"; break; case PCIC_VG465: cinfo.name = "Vadem 465"; break; case PCIC_VG468: cinfo.name = "Vadem 468"; break; case PCIC_VG469: cinfo.name = "Vadem 469"; break; case PCIC_RF5C396: cinfo.name = "Ricoh RF5C396"; break; case PCIC_VLSI: cinfo.name = "VLSI 82C146"; break; default: cinfo.name = "Unknown!"; break; } /* * OK it seems we have a PCIC or lookalike. * Allocate a slot and initialise the data structures. */ validslots++; sp->slotnum = slotnum; slt = pccard_alloc_slot(&cinfo); if (slt == 0) continue; slt->cdata = sp; sp->slt = slt; /* * If we haven't allocated an interrupt for the controller, * then attempt to get one. */ if (pcic_irq == 0) { pcic_imask = soft_imask; /* See if the user has requested a specific IRQ */ if (getenv_int("machdep.pccard.pcic_irq", &desired_irq)) /* legal IRQ? */ if ((desired_irq >= 1) && (desired_irq <= ICU_LEN) && ((1ul << desired_irq) & soft_imask)) pcic_imask = 1ul << desired_irq; else /* illeagal, disable use of IRQ */ pcic_imask = 0; pcic_irq = pccard_alloc_intr(free_irqs, pcicintr, 0, &pcic_imask, NULL); if (pcic_irq < 0) printf("pcic: failed to allocate IRQ\n"); else printf("pcic: controller irq %d\n", pcic_irq); } /* * Modem cards send the speaker audio (dialing noises) * to the host's speaker. Cirrus Logic PCIC chips must * enable this. There is also a Low Power Dynamic Mode bit * that claims to reduce power consumption by 30%, so * enable it and hope for the best. */ if (sp->controller == PCIC_PD672X) { setb(sp, PCIC_MISC1, PCIC_SPKR_EN); setb(sp, PCIC_MISC2, PCIC_LPDM_EN); } /* * Check for a card in this slot. */ setb(sp, PCIC_POWER, PCIC_PCPWRE| PCIC_DISRST); if ((sp->getb(sp, PCIC_STATUS) & PCIC_CD) != PCIC_CD) { slt->laststate = slt->state = empty; } else { slt->laststate = slt->state = filled; pccard_event(sp->slt, card_inserted); } /* * Assign IRQ for slot changes */ if (pcic_irq > 0) sp->putb(sp, PCIC_STAT_INT, (pcic_irq << 4) | 0xF); } #ifdef PC98 if (validslots == 0) { sp = pcic_slots; slotnum = 0; if (inb(PCIC98_REG0) != 0xff) { sp->controller = PCIC_PC98; sp->revision = 0; cinfo.name = "PC98 Original"; cinfo.maxmem = 1; cinfo.maxio = 1; /* cinfo.irqs = PCIC_INT_MASK_ALLOWED;*/ cinfo.irqs = 0x1468; validslots++; sp->slotnum = slotnum; slt = pccard_alloc_slot(&cinfo); if (slt == 0) { printf("pcic98: slt == NULL\n"); goto pcic98_probe_end; } slt->cdata = sp; sp->slt = slt; /* Check for a card in this slot */ if (inb(PCIC98_REG1) & PCIC98_CARDEXIST) { /* PCMCIA card exist */ slt->laststate = slt->state = filled; pccard_event(sp->slt, card_inserted); } else { slt->laststate = slt->state = empty; } } pcic98_probe_end: } #endif /* PC98 */ if (validslots && pcic_irq <= 0) pcictimeout_ch = timeout(pcictimeout, 0, hz/2); return(validslots); } /* * ioctl calls - Controller specific ioctls */ static int pcic_ioctl(struct slot *slt, int cmd, caddr_t data) { struct pcic_slot *sp = slt->cdata; switch(cmd) { default: return(EINVAL); /* * Get/set PCIC registers */ case PIOCGREG: ((struct pcic_reg *)data)->value = sp->getb(sp, ((struct pcic_reg *)data)->reg); break; case PIOCSREG: sp->putb(sp, ((struct pcic_reg *)data)->reg, ((struct pcic_reg *)data)->value); break; } return(0); } /* * pcic_power - Enable the power of the slot according to * the parameters in the power structure(s). */ static int pcic_power(struct slot *slt) { unsigned char reg = PCIC_DISRST|PCIC_PCPWRE; struct pcic_slot *sp = slt->cdata; switch(sp->controller) { #ifdef PC98 case PCIC_PC98: reg = inb(PCIC98_REG6) & (~PCIC98_VPP12V); switch(slt->pwr.vpp) { default: return(EINVAL); case 50: break; case 120: reg |= PCIC98_VPP12V; break; } outb(PCIC98_REG6, reg); DELAY(100*1000); reg = inb(PCIC98_REG2) & (~PCIC98_VCC3P3V); switch(slt->pwr.vcc) { default: return(EINVAL); case 33: reg |= PCIC98_VCC3P3V; break; case 50: break; } outb(PCIC98_REG2, reg); DELAY(100*1000); return (0); #endif case PCIC_PD672X: case PCIC_PD6710: case PCIC_VG365: case PCIC_VG465: case PCIC_VG468: case PCIC_VG469: case PCIC_RF5C396: case PCIC_VLSI: case PCIC_IBM_KING: switch(slt->pwr.vpp) { default: return(EINVAL); case 0: break; case 50: case 33: reg |= PCIC_VPP_5V; break; case 120: reg |= PCIC_VPP_12V; break; } switch(slt->pwr.vcc) { default: return(EINVAL); case 0: break; case 33: if (sp->controller == PCIC_IBM_KING) { reg |= PCIC_VCC_5V_KING; break; } reg |= PCIC_VCC_3V; if ((sp->controller == PCIC_VG468) || (sp->controller == PCIC_VG469) || (sp->controller == PCIC_VG465) || (sp->controller == PCIC_VG365)) setb(sp, 0x2f, 0x03) ; else setb(sp, 0x16, 0x02); break; case 50: if (sp->controller == PCIC_IBM_KING) { reg |= PCIC_VCC_5V_KING; break; } reg |= PCIC_VCC_5V; if ((sp->controller == PCIC_VG468) || (sp->controller == PCIC_VG469) || (sp->controller == PCIC_VG465) || (sp->controller == PCIC_VG365)) clrb(sp, 0x2f, 0x03) ; else clrb(sp, 0x16, 0x02); break; } break; } sp->putb(sp, PCIC_POWER, reg); DELAY(300*1000); if (slt->pwr.vcc) { reg |= PCIC_OUTENA; sp->putb(sp, PCIC_POWER, reg); DELAY(100*1000); } /* Some chips are smarter than us it seems, so if we weren't * allowed to use 5V, try 3.3 instead */ if (!(sp->getb(sp, PCIC_STATUS) & 0x40) && slt->pwr.vcc == 50) { slt->pwr.vcc = 33; slt->pwr.vpp = 0; return (pcic_power(slt)); } return(0); } /* * tell the PCIC which irq we want to use. only the following are legal: * 3, 4, 5, 7, 9, 10, 11, 12, 14, 15 */ static void pcic_mapirq(struct slot *slt, int irq) { struct pcic_slot *sp = slt->cdata; #ifdef PC98 if (sp->controller == PCIC_PC98) { unsigned char x; switch (irq) { case 3: x = PCIC98_INT0; break; case 5: x = PCIC98_INT1; break; case 6: x = PCIC98_INT2; break; case 10: x = PCIC98_INT4; break; case 12: x = PCIC98_INT5; break; case 0: /* disable */ x = PCIC98_INTDISABLE; break; default: printf("pcic98: illegal irq %d\n", irq); return; } #ifdef PCIC_DEBUG printf("pcic98: irq=%d mapped.\n", irq); #endif outb(PCIC98_REG3, x); return; } #endif if (irq == 0) clrb(sp, PCIC_INT_GEN, 0xF); else sp->putb(sp, PCIC_INT_GEN, (sp->getb(sp, PCIC_INT_GEN) & 0xF0) | irq); } /* * pcic_reset - Reset the card and enable initial power. */ static void pcic_reset(void *chan) { struct slot *slt = chan; struct pcic_slot *sp = slt->cdata; #ifdef PC98 if (sp->controller == PCIC_PC98) { outb(PCIC98_REG0, 0); outb(PCIC98_REG2, inb(PCIC98_REG2) & (~PCIC98_IOMEMORY)); outb(PCIC98_REG3, PCIC98_INTDISABLE); outb(PCIC98_REG2, inb(PCIC98_REG2) & (~PCIC98_VCC3P3V)); outb(PCIC98_REG6, inb(PCIC98_REG6) & (~PCIC98_VPP12V)); outb(PCIC98_REG1, 0); selwakeup(&slt->selp); return; } #endif switch (slt->insert_seq) { case 0: /* Something funny happended on the way to the pub... */ return; case 1: /* Assert reset */ clrb(sp, PCIC_INT_GEN, PCIC_CARDRESET); slt->insert_seq = 2; timeout(pcic_reset, (void *)slt, hz/4); return; case 2: /* Deassert it again */ setb(sp, PCIC_INT_GEN, PCIC_CARDRESET|PCIC_IOCARD); slt->insert_seq = 3; timeout(pcic_reset, (void *)slt, hz/4); return; case 3: /* Wait if card needs more time */ if (!sp->getb(sp, PCIC_STATUS) & PCIC_READY) { timeout(pcic_reset, (void *)slt, hz/10); return; } } slt->insert_seq = 0; if (sp->controller == PCIC_PD672X || sp->controller == PCIC_PD6710) { sp->putb(sp, PCIC_TIME_SETUP0, 0x1); sp->putb(sp, PCIC_TIME_CMD0, 0x6); sp->putb(sp, PCIC_TIME_RECOV0, 0x0); sp->putb(sp, PCIC_TIME_SETUP1, 1); sp->putb(sp, PCIC_TIME_CMD1, 0xf); sp->putb(sp, PCIC_TIME_RECOV1, 0); } selwakeup(&slt->selp); } /* * pcic_disable - Disable the slot. */ static void pcic_disable(struct slot *slt) { struct pcic_slot *sp = slt->cdata; #ifdef PC98 if (sp->controller == PCIC_PC98) { return; } #endif sp->putb(sp, PCIC_INT_GEN, 0); sp->putb(sp, PCIC_POWER, 0); } /* * PCIC timer. If the controller doesn't have a free IRQ to use * or if interrupt steering doesn't work, poll the controller for * insertion/removal events. */ static void pcictimeout(void *chan) { pcicintr(NULL); pcictimeout_ch = timeout(pcictimeout, 0, hz/2); } /* * PCIC Interrupt handler. * Check each slot in turn, and read the card status change * register. If this is non-zero, then a change has occurred * on this card, so send an event to the main code. */ static void pcicintr(void *unused) { int slot, s; unsigned char chg; struct pcic_slot *sp = pcic_slots; #ifdef PC98 if (sp->controller == PCIC_PC98) { slot = 0; s = splhigh(); /* Check for a card in this slot */ if (inb(PCIC98_REG1) & PCIC98_CARDEXIST) { if (sp->slt->laststate != filled) { pccard_event(sp->slt, card_inserted); } } else { if (sp->slt->laststate != empty) { pccard_event(sp->slt, card_removed); } } splx(s); return; } #endif /* PC98 */ s = splhigh(); for (slot = 0; slot < PCIC_MAX_SLOTS; slot++, sp++) if (sp->slt && (chg = sp->getb(sp, PCIC_STAT_CHG)) != 0) if (chg & PCIC_CDTCH) { if ((sp->getb(sp, PCIC_STATUS) & PCIC_CD) == PCIC_CD) { pccard_event(sp->slt, card_inserted); } else { pccard_event(sp->slt, card_removed); } } splx(s); } /* * pcic_resume - Suspend/resume support for PCIC */ static void pcic_resume(struct slot *slt) { struct pcic_slot *sp = slt->cdata; if (pcic_irq > 0) sp->putb(sp, PCIC_STAT_INT, (pcic_irq << 4) | 0xF); if (sp->controller == PCIC_PD672X) { setb(sp, PCIC_MISC1, PCIC_SPKR_EN); setb(sp, PCIC_MISC2, PCIC_LPDM_EN); } }