|
|
| version 1.7, 2003/08/07 21:16:46 | version 1.8, 2004/02/10 07:55:45 |
|---|---|
| Line 1 | Line 1 |
| /* $NetBSD: pcmcia.c,v 1.23 2000/07/28 19:17:02 drochner Exp $ */ | |
| /* $FreeBSD: src/sys/dev/pccard/pccard.c,v 1.70 2002/11/14 14:02:32 mux Exp $ */ | |
| /* $DragonFly$ | |
| /* | /* |
| * pccard.c - Interface code for PC-CARD controllers. | * Copyright (c) 1997 Marc Horowitz. All rights reserved. |
| * | |
| * June 1995, Andrew McRae (andrew@mega.com.au) | |
| *------------------------------------------------------------------------- | |
| * | |
| * Copyright (c) 2001 M. Warner Losh. All rights reserved. | |
| * Copyright (c) 1995 Andrew McRae. All rights reserved. | |
| * | * |
| * Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions |
| Line 15 | Line 13 |
| * 2. Redistributions in binary form must reproduce the above copyright | * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the | * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. | * documentation and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products | * 3. All advertising materials mentioning features or use of this software |
| * must display the following acknowledgement: | |
| * This product includes software developed by Marc Horowitz. | |
| * 4. The name of the author may not be used to endorse or promote products | |
| * derived from this software without specific prior written permission. | * derived from this software without specific prior written permission. |
| * | * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| Line 28 | Line 29 |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * | |
| * $FreeBSD: src/sys/pccard/pccard.c,v 1.106.2.15 2003/02/26 18:42:00 imp Exp $ | |
| * $DragonFly$ | |
| */ | */ |
| #include <sys/param.h> | #include <sys/param.h> |
| #include <sys/types.h> | |
| #include <sys/systm.h> | #include <sys/systm.h> |
| #include <sys/kernel.h> | |
| #include <sys/malloc.h> | #include <sys/malloc.h> |
| #include <sys/module.h> | |
| #include <sys/kernel.h> | |
| #include <sys/queue.h> | |
| #include <sys/sysctl.h> | #include <sys/sysctl.h> |
| #include <sys/conf.h> | #include <sys/types.h> |
| #include <sys/uio.h> | |
| #include <sys/poll.h> | |
| #include <sys/bus.h> | #include <sys/bus.h> |
| #include <sys/proc.h> | |
| #include <machine/bus.h> | #include <machine/bus.h> |
| #include <sys/rman.h> | |
| #include <machine/resource.h> | |
| #include "cardinfo.h" | #include <net/ethernet.h> |
| #include "driver.h" | |
| #include "slot.h" | |
| #include "pccard_nbk.h" | |
| #include <machine/md_var.h> | |
| static int allocate_driver(struct slot *, struct dev_desc *); | |
| static void inserted(void *); | |
| static void disable_slot(struct slot *); | |
| static void disable_slot_to(struct slot *); | |
| static void power_off_slot(void *); | |
| /* | #include <bus/pccard/pccardreg.h> |
| * The driver interface for read/write uses a block | #include <bus/pccard/pccardvar.h> |
| * of memory in the ISA I/O memory space allocated via | |
| * an ioctl setting. | #include "power_if.h" |
| * | #include "card_if.h" |
| * Now that we have different bus attachments, we should really | |
| * use a better algorythm to allocate memory. | #define PCCARDDEBUG |
| */ | |
| static unsigned long pccard_mem; /* Physical memory */ | /* sysctl vars */ |
| static unsigned char *pccard_kmem; /* Kernel virtual address */ | SYSCTL_NODE(_hw, OID_AUTO, pccard, CTLFLAG_RD, 0, "PCCARD parameters"); |
| static struct resource *pccard_mem_res; | |
| static int pccard_mem_rid; | int pccard_debug = 0; |
| TUNABLE_INT("hw.pccard.debug", &pccard_debug); | |
| static d_open_t crdopen; | SYSCTL_INT(_hw_pccard, OID_AUTO, debug, CTLFLAG_RW, |
| static d_close_t crdclose; | &pccard_debug, 0, |
| static d_read_t crdread; | "pccard debug"); |
| static d_write_t crdwrite; | |
| static d_ioctl_t crdioctl; | int pccard_cis_debug = 0; |
| static d_poll_t crdpoll; | TUNABLE_INT("hw.pccard.cis_debug", &pccard_cis_debug); |
| SYSCTL_INT(_hw_pccard, OID_AUTO, cis_debug, CTLFLAG_RW, | |
| #define CDEV_MAJOR 50 | &pccard_cis_debug, 0, "pccard CIS debug"); |
| static struct cdevsw crd_cdevsw = { | |
| /* name */ "crd", | #ifdef PCCARDDEBUG |
| /* maj */ CDEV_MAJOR, | #define DPRINTF(arg) if (pccard_debug) printf arg |
| /* flags */ 0, | #define DEVPRINTF(arg) if (pccard_debug) device_printf arg |
| /* port */ NULL, | #define PRVERBOSE(arg) printf arg |
| /* autoq */ 0, | #define DEVPRVERBOSE(arg) device_printf arg |
| #else | |
| /* open */ crdopen, | #define DPRINTF(arg) |
| /* close */ crdclose, | #define DEVPRINTF(arg) |
| /* read */ crdread, | #define PRVERBOSE(arg) if (bootverbose) printf arg |
| /* write */ crdwrite, | #define DEVPRVERBOSE(arg) if (bootverbose) device_printf arg |
| /* ioctl */ crdioctl, | #endif |
| /* poll */ crdpoll, | |
| /* mmap */ nommap, | static int pccard_ccr_read(struct pccard_function *pf, int ccr); |
| /* strategy */ nostrategy, | static void pccard_ccr_write(struct pccard_function *pf, int ccr, int val); |
| /* dump */ nodump, | static int pccard_attach_card(device_t dev); |
| /* psize */ nopsize | static int pccard_detach_card(device_t dev); |
| }; | static int pccard_card_gettype(device_t dev, int *type); |
| static void pccard_function_init(struct pccard_function *pf); | |
| static void pccard_function_free(struct pccard_function *pf); | |
| static int pccard_function_enable(struct pccard_function *pf); | |
| static void pccard_function_disable(struct pccard_function *pf); | |
| static int pccard_compat_do_probe(device_t bus, device_t dev); | |
| static int pccard_compat_do_attach(device_t bus, device_t dev); | |
| static int pccard_add_children(device_t dev, int busno); | |
| static int pccard_probe(device_t dev); | |
| static int pccard_attach(device_t dev); | |
| static int pccard_detach(device_t dev); | |
| static void pccard_print_resources(struct resource_list *rl, | |
| const char *name, int type, int count, const char *format); | |
| static int pccard_print_child(device_t dev, device_t child); | |
| static int pccard_set_resource(device_t dev, device_t child, int type, | |
| int rid, u_long start, u_long count); | |
| static int pccard_get_resource(device_t dev, device_t child, int type, | |
| int rid, u_long *startp, u_long *countp); | |
| static void pccard_delete_resource(device_t dev, device_t child, int type, | |
| int rid); | |
| static int pccard_set_res_flags(device_t dev, device_t child, int type, | |
| int rid, u_int32_t flags); | |
| static int pccard_set_memory_offset(device_t dev, device_t child, int rid, | |
| u_int32_t offset, u_int32_t *deltap); | |
| static void pccard_probe_nomatch(device_t cbdev, device_t child); | |
| static int pccard_read_ivar(device_t bus, device_t child, int which, | |
| u_char *result); | |
| static void pccard_driver_added(device_t dev, driver_t *driver); | |
| static struct resource *pccard_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 pccard_release_resource(device_t dev, device_t child, int type, | |
| int rid, struct resource *r); | |
| static void pccard_child_detached(device_t parent, device_t dev); | |
| static void pccard_intr(void *arg); | |
| static int pccard_setup_intr(device_t dev, device_t child, | |
| struct resource *irq, int flags, driver_intr_t *intr, | |
| void *arg, void **cookiep); | |
| static int pccard_teardown_intr(device_t dev, device_t child, | |
| struct resource *r, void *cookie); | |
| static const struct pccard_product * | |
| pccard_do_product_lookup(device_t bus, device_t dev, | |
| const struct pccard_product *tab, size_t ent_size, | |
| pccard_product_match_fn matchfn); | |
| static int | |
| pccard_ccr_read(struct pccard_function *pf, int ccr) | |
| { | |
| return (bus_space_read_1(pf->pf_ccrt, pf->pf_ccrh, | |
| pf->pf_ccr_offset + ccr)); | |
| } | |
| /* | |
| * Power off the slot. | |
| * (doing it immediately makes the removal of some cards unstable) | |
| */ | |
| static void | static void |
| power_off_slot(void *arg) | pccard_ccr_write(struct pccard_function *pf, int ccr, int val) |
| { | { |
| struct slot *slt = (struct slot *)arg; | if ((pf->ccr_mask) & (1 << (ccr / 2))) { |
| int s; | bus_space_write_1(pf->pf_ccrt, pf->pf_ccrh, |
| pf->pf_ccr_offset + ccr, val); | |
| } | |
| } | |
| static int | |
| pccard_attach_card(device_t dev) | |
| { | |
| struct pccard_softc *sc = PCCARD_SOFTC(dev); | |
| struct pccard_function *pf; | |
| struct pccard_ivar *ivar; | |
| device_t child; | |
| int i; | |
| /* | /* |
| * The following will generate an interrupt. So, to hold off | * this is here so that when socket_enable calls gettype, trt happens |
| * the interrupt unitl after disable runs so that we can get rid | |
| * rid of the interrupt before it becomes unsafe to touch the | |
| * device. | |
| * | |
| * XXX In current, the spl stuff is a nop. | |
| */ | */ |
| s = splhigh(); | STAILQ_INIT(&sc->card.pf_head); |
| /* Power off the slot. */ | |
| slt->pwr_off_pending = 0; | DEVPRINTF((dev, "chip_socket_enable\n")); |
| slt->ctrl->disable(slt); | POWER_ENABLE_SOCKET(device_get_parent(dev), dev); |
| splx(s); | |
| DEVPRINTF((dev, "read_cis\n")); | |
| pccard_read_cis(sc); | |
| DEVPRINTF((dev, "check_cis_quirks\n")); | |
| pccard_check_cis_quirks(dev); | |
| /* | |
| * bail now if the card has no functions, or if there was an error in | |
| * the cis. | |
| */ | |
| if (sc->card.error) { | |
| device_printf(dev, "CARD ERROR!\n"); | |
| return (1); | |
| } | |
| if (STAILQ_EMPTY(&sc->card.pf_head)) { | |
| device_printf(dev, "Card has no functions!\n"); | |
| return (1); | |
| } | |
| if (bootverbose || pccard_debug) | |
| pccard_print_cis(dev); | |
| DEVPRINTF((dev, "functions scanning\n")); | |
| i = -1; | |
| STAILQ_FOREACH(pf, &sc->card.pf_head, pf_list) { | |
| i++; | |
| if (STAILQ_EMPTY(&pf->cfe_head)) { | |
| device_printf(dev, | |
| "Function %d has no config entries.!\n", i); | |
| continue; | |
| } | |
| pf->sc = sc; | |
| pf->cfe = NULL; | |
| pf->dev = NULL; | |
| } | |
| DEVPRINTF((dev, "Card has %d functions. pccard_mfc is %d\n", i + 1, | |
| pccard_mfc(sc))); | |
| STAILQ_FOREACH(pf, &sc->card.pf_head, pf_list) { | |
| if (STAILQ_EMPTY(&pf->cfe_head)) | |
| continue; | |
| /* | |
| * In NetBSD, the drivers are responsible for activating | |
| * each function of a card. I think that in FreeBSD we | |
| * want to activate them enough for the usual bus_*_resource | |
| * routines will do the right thing. This many mean a | |
| * departure from the current NetBSD model. | |
| * | |
| * This seems to work well in practice for most cards. | |
| * However, there are two cases that are problematic. | |
| * If a driver wishes to pick and chose which config | |
| * entry to use, then this method falls down. These | |
| * are usually older cards. In addition, there are | |
| * some cards that have multiple hardware units on the | |
| * cards, but presents only one CIS chain. These cards | |
| * are combination cards, but only one of these units | |
| * can be on at a time. | |
| */ | |
| ivar = malloc(sizeof(struct pccard_ivar), M_DEVBUF, | |
| M_WAITOK | M_ZERO); | |
| child = device_add_child(dev, NULL, -1); | |
| device_set_ivars(child, ivar); | |
| ivar->fcn = pf; | |
| pf->dev = child; | |
| /* | |
| * XXX We might want to move the next two lines into | |
| * XXX the pccard interface layer. For the moment, this | |
| * XXX is OK, but some drivers want to pick the config | |
| * XXX entry to use as well as some address tweaks (mostly | |
| * XXX due to bugs in decode logic that makes some | |
| * XXX addresses illegal or broken). | |
| */ | |
| pccard_function_init(pf); | |
| if (sc->sc_enabled_count == 0) | |
| POWER_ENABLE_SOCKET(device_get_parent(dev), dev); | |
| if (pccard_function_enable(pf) == 0 && | |
| device_probe_and_attach(child) == 0) { | |
| DEVPRINTF((sc->dev, "function %d CCR at %d " | |
| "offset %x: %x %x %x %x, %x %x %x %x, %x\n", | |
| pf->number, pf->pf_ccr_window, pf->pf_ccr_offset, | |
| pccard_ccr_read(pf, 0x00), | |
| pccard_ccr_read(pf, 0x02), pccard_ccr_read(pf, 0x04), | |
| pccard_ccr_read(pf, 0x06), pccard_ccr_read(pf, 0x0A), | |
| pccard_ccr_read(pf, 0x0C), pccard_ccr_read(pf, 0x0E), | |
| pccard_ccr_read(pf, 0x10), pccard_ccr_read(pf, 0x12))); | |
| } else { | |
| if (pf->cfe != NULL) | |
| pccard_function_disable(pf); | |
| } | |
| } | |
| return (0); | |
| } | } |
| /* | static int |
| * disable_slot - Disables the slot by removing | pccard_detach_card(device_t dev) |
| * the power and unmapping the I/O | |
| */ | |
| static void | |
| disable_slot(struct slot *slt) | |
| { | { |
| device_t pccarddev; | struct pccard_softc *sc = PCCARD_SOFTC(dev); |
| device_t *kids; | struct pccard_function *pf; |
| int nkids; | struct pccard_config_entry *cfe; |
| int i; | |
| int ret; | |
| /* | /* |
| * Note that a race condition is possible here; if a | * We are running on either the PCCARD socket's event thread |
| * driver is accessing the device and it is removed, then | * or in user context detaching a device by user request. |
| * all bets are off... | |
| */ | */ |
| pccarddev = slt->dev; | STAILQ_FOREACH(pf, &sc->card.pf_head, pf_list) { |
| device_get_children(pccarddev, &kids, &nkids); | int state = device_get_state(pf->dev); |
| for (i = 0; i < nkids; i++) { | |
| if ((ret = device_delete_child(pccarddev, kids[i])) != 0) | if (state == DS_ATTACHED || state == DS_BUSY) |
| printf("pccard: delete of %s failed: %d\n", | device_detach(pf->dev); |
| device_get_nameunit(kids[i]), ret); | if (pf->cfe != NULL) |
| } | pccard_function_disable(pf); |
| free(kids, M_TEMP); | pccard_function_free(pf); |
| device_delete_child(dev, pf->dev); | |
| /* Power off the slot 1/2 second after removal of the card */ | } |
| slt->poff_ch = timeout(power_off_slot, (caddr_t)slt, hz / 2); | if (sc->sc_enabled_count == 0) |
| slt->pwr_off_pending = 1; | POWER_DISABLE_SOCKET(device_get_parent(dev), dev); |
| while (NULL != (pf = STAILQ_FIRST(&sc->card.pf_head))) { | |
| while (NULL != (cfe = STAILQ_FIRST(&pf->cfe_head))) { | |
| STAILQ_REMOVE_HEAD(&pf->cfe_head, cfe_list); | |
| free(cfe, M_DEVBUF); | |
| } | |
| STAILQ_REMOVE_HEAD(&sc->card.pf_head, pf_list); | |
| free(pf, M_DEVBUF); | |
| } | |
| return (0); | |
| } | } |
| static void | static const struct pccard_product * |
| disable_slot_to(struct slot *slt) | pccard_do_product_lookup(device_t bus, device_t dev, |
| const struct pccard_product *tab, size_t ent_size, | |
| pccard_product_match_fn matchfn) | |
| { | { |
| disable_slot(slt); | const struct pccard_product *ent; |
| if (slt->state == empty) | int matches; |
| printf("pccard: card removed, slot %d\n", slt->slotnum); | u_int32_t fcn; |
| u_int32_t vendor; | |
| u_int32_t prod; | |
| char *vendorstr; | |
| char *prodstr; | |
| #ifdef DIAGNOSTIC | |
| if (sizeof *ent > ent_size) | |
| panic("pccard_product_lookup: bogus ent_size %ld", | |
| (long) ent_size); | |
| #endif | |
| if (pccard_get_vendor(dev, &vendor)) | |
| return (NULL); | |
| if (pccard_get_product(dev, &prod)) | |
| return (NULL); | |
| if (pccard_get_function_number(dev, &fcn)) | |
| return (NULL); | |
| if (pccard_get_vendor_str(dev, &vendorstr)) | |
| return (NULL); | |
| if (pccard_get_product_str(dev, &prodstr)) | |
| return (NULL); | |
| for (ent = tab; ent->pp_name != NULL; ent = | |
| (const struct pccard_product *) ((const char *) ent + ent_size)) { | |
| matches = 1; | |
| if (ent->pp_vendor == PCCARD_VENDOR_ANY && | |
| ent->pp_product == PCCARD_VENDOR_ANY && | |
| ent->pp_cis[0] == NULL && | |
| ent->pp_cis[1] == NULL) { | |
| device_printf(dev, | |
| "Total wildcard entry ignored for %s\n", | |
| ent->pp_name); | |
| continue; | |
| } | |
| if (matches && ent->pp_vendor != PCCARD_VENDOR_ANY && | |
| vendor != ent->pp_vendor) | |
| matches = 0; | |
| if (matches && ent->pp_product != PCCARD_PRODUCT_ANY && | |
| prod != ent->pp_product) | |
| matches = 0; | |
| if (matches && fcn != ent->pp_expfunc) | |
| matches = 0; | |
| if (matches && ent->pp_cis[0] && | |
| strcmp(ent->pp_cis[0], vendorstr) != 0) | |
| matches = 0; | |
| if (matches && ent->pp_cis[1] && | |
| strcmp(ent->pp_cis[1], prodstr) != 0) | |
| matches = 0; | |
| /* XXX need to match cis[2] and cis[3] also XXX */ | |
| if (matchfn != NULL) | |
| matches = (*matchfn)(dev, ent, matches); | |
| if (matches) | |
| return (ent); | |
| } | |
| return (NULL); | |
| } | |
| static int | |
| pccard_card_gettype(device_t dev, int *type) | |
| { | |
| struct pccard_softc *sc = PCCARD_SOFTC(dev); | |
| struct pccard_function *pf; | |
| /* | |
| * set the iftype to memory if this card has no functions (not yet | |
| * probed), or only one function, and that is not initialized yet or | |
| * that is memory. | |
| */ | |
| pf = STAILQ_FIRST(&sc->card.pf_head); | |
| if (pf == NULL || | |
| (STAILQ_NEXT(pf, pf_list) == NULL && | |
| (pf->cfe == NULL || pf->cfe->iftype == PCCARD_IFTYPE_MEMORY))) | |
| *type = PCCARD_IFTYPE_MEMORY; | |
| else | else |
| printf("pccard: card deactivated, slot %d\n", slt->slotnum); | *type = PCCARD_IFTYPE_IO; |
| pccard_remove_beep(); | return (0); |
| selwakeup(&slt->selp); | |
| } | } |
| /* | /* |
| * pccard_init_slot - Initialize the slot controller and attach various | * Initialize a PCCARD function. May be called as long as the function is |
| * things to it. We also make the device for it. We create the device that | * disabled. |
| * will be exported to devfs. | * |
| * Note: pccard_function_init should not keep resources allocated. It should | |
| * only set them up ala isa pnp, set the values in the rl lists, and return. | |
| * Any resource held after pccard_function_init is called is a bug. However, | |
| * the bus routines to get the resources also assume that pccard_function_init | |
| * does this, so they need to be fixed too. | |
| */ | */ |
| struct slot * | static void |
| pccard_init_slot(device_t dev, struct slot_ctrl *ctrl) | pccard_function_init(struct pccard_function *pf) |
| { | { |
| int slotno; | struct pccard_config_entry *cfe; |
| struct slot *slt; | int i; |
| struct pccard_ivar *devi = PCCARD_IVAR(pf->dev); | |
| slt = PCCARD_DEVICE2SOFTC(dev); | struct resource_list *rl = &devi->resources; |
| slotno = device_get_unit(dev); | struct resource_list_entry *rle; |
| slt->dev = dev; | struct resource *r = 0; |
| slt->d = make_dev(&crd_cdevsw, slotno, 0, 0, 0600, "card%d", slotno); | device_t bus; |
| slt->d->si_drv1 = slt; | int start; |
| slt->ctrl = ctrl; | int end; |
| slt->slotnum = slotno; | int spaces; |
| callout_handle_init(&slt->insert_ch); | |
| callout_handle_init(&slt->poff_ch); | if (pf->pf_flags & PFF_ENABLED) { |
| printf("pccard_function_init: function is enabled"); | |
| return (slt); | return; |
| } | |
| bus = device_get_parent(pf->dev); | |
| /* Remember which configuration entry we are using. */ | |
| STAILQ_FOREACH(cfe, &pf->cfe_head, cfe_list) { | |
| for (i = 0; i < cfe->num_iospace; i++) | |
| cfe->iores[i] = NULL; | |
| cfe->irqres = NULL; | |
| spaces = 0; | |
| for (i = 0; i < cfe->num_iospace; i++) { | |
| start = cfe->iospace[i].start; | |
| if (start) | |
| end = start + cfe->iospace[i].length - 1; | |
| else | |
| end = ~0; | |
| cfe->iorid[i] = i; | |
| DEVPRINTF((bus, "I/O rid %d start %x end %x\n", | |
| i, start, end)); | |
| r = cfe->iores[i] = bus_alloc_resource(bus, | |
| SYS_RES_IOPORT, &cfe->iorid[i], start, end, | |
| cfe->iospace[i].length, | |
| rman_make_alignment_flags(cfe->iospace[i].length)); | |
| if (cfe->iores[i] == NULL) | |
| goto not_this_one; | |
| resource_list_add(rl, SYS_RES_IOPORT, cfe->iorid[i], | |
| rman_get_start(r), rman_get_end(r), | |
| cfe->iospace[i].length); | |
| rle = resource_list_find(rl, SYS_RES_IOPORT, | |
| cfe->iorid[i]); | |
| rle->res = r; | |
| spaces++; | |
| } | |
| if (cfe->num_memspace > 0) { | |
| /* | |
| * Not implement yet, Fix me. | |
| */ | |
| DEVPRINTF((bus, "Memory space not yet implemented.\n")); | |
| } | |
| if (spaces == 0) { | |
| DEVPRINTF((bus, "Neither memory nor I/O mampped\n")); | |
| goto not_this_one; | |
| } | |
| if (cfe->irqmask) { | |
| cfe->irqrid = 0; | |
| r = cfe->irqres = bus_alloc_resource(bus, SYS_RES_IRQ, | |
| &cfe->irqrid, 0, ~0, 1, 0); | |
| if (cfe->irqres == NULL) | |
| goto not_this_one; | |
| resource_list_add(rl, SYS_RES_IRQ, cfe->irqrid, | |
| rman_get_start(r), rman_get_end(r), 1); | |
| rle = resource_list_find(rl, SYS_RES_IRQ, | |
| cfe->irqrid); | |
| rle->res = r; | |
| } | |
| /* If we get to here, we've allocated all we need */ | |
| pf->cfe = cfe; | |
| break; | |
| not_this_one:; | |
| DEVPRVERBOSE((bus, "Allocation failed for cfe %d\n", | |
| cfe->number)); | |
| /* | |
| * Release resources that we partially allocated | |
| * from this config entry. | |
| */ | |
| for (i = 0; i < cfe->num_iospace; i++) { | |
| if (cfe->iores[i] != NULL) { | |
| bus_release_resource(bus, SYS_RES_IOPORT, | |
| cfe->iorid[i], cfe->iores[i]); | |
| rle = resource_list_find(rl, SYS_RES_IOPORT, | |
| cfe->iorid[i]); | |
| rle->res = NULL; | |
| resource_list_delete(rl, SYS_RES_IOPORT, | |
| cfe->iorid[i]); | |
| } | |
| cfe->iores[i] = NULL; | |
| } | |
| if (cfe->irqmask && cfe->irqres != NULL) { | |
| bus_release_resource(bus, SYS_RES_IRQ, | |
| cfe->irqrid, cfe->irqres); | |
| rle = resource_list_find(rl, SYS_RES_IRQ, | |
| cfe->irqrid); | |
| rle->res = NULL; | |
| resource_list_delete(rl, SYS_RES_IRQ, cfe->irqrid); | |
| cfe->irqres = NULL; | |
| } | |
| } | |
| } | } |
| /* | /* |
| * allocate_driver - Create a new device entry for this | * Free resources allocated by pccard_function_init(), May be called as long |
| * slot, and attach a driver to it. | * as the function is disabled. |
| * | |
| * NOTE: This function should be unnecessary. pccard_function_init should | |
| * never keep resources initialized. | |
| */ | */ |
| static void | |
| pccard_function_free(struct pccard_function *pf) | |
| { | |
| struct pccard_ivar *devi = PCCARD_IVAR(pf->dev); | |
| struct resource_list_entry *rle; | |
| if (pf->pf_flags & PFF_ENABLED) { | |
| printf("pccard_function_init: function is enabled"); | |
| return; | |
| } | |
| SLIST_FOREACH(rle, &devi->resources, link) { | |
| if (rle->res) { | |
| if (rle->res->r_dev != pf->sc->dev) | |
| device_printf(pf->sc->dev, | |
| "function_free: Resource still owned by " | |
| "child, oops. " | |
| "(type=%d, rid=%d, addr=%lx)\n", | |
| rle->type, rle->rid, | |
| rman_get_start(rle->res)); | |
| BUS_RELEASE_RESOURCE(device_get_parent(pf->sc->dev), | |
| rle->res->r_dev, rle->type, rle->rid, rle->res); | |
| rle->res = NULL; | |
| } | |
| } | |
| resource_list_free(&devi->resources); | |
| } | |
| /* Enable a PCCARD function */ | |
| static int | static int |
| allocate_driver(struct slot *slt, struct dev_desc *desc) | pccard_function_enable(struct pccard_function *pf) |
| { | { |
| struct pccard_devinfo *devi; | struct pccard_function *tmp; |
| device_t pccarddev; | int reg; |
| int err, irq = 0; | device_t dev = pf->sc->dev; |
| device_t child; | |
| device_t *devs; | if (pf->cfe == NULL) { |
| int count; | DEVPRVERBOSE((dev, "No config entry could be allocated.\n")); |
| return (ENOMEM); | |
| } | |
| pccarddev = slt->dev; | |
| err = device_get_children(pccarddev, &devs, &count); | |
| if (err != 0) | |
| return (err); | |
| free(devs, M_TEMP); | |
| if (count) { | |
| device_printf(pccarddev, | |
| "Can not attach more than one child.\n"); | |
| return (EIO); | |
| } | |
| irq = ffs(desc->irqmask) - 1; | |
| MALLOC(devi, struct pccard_devinfo *, sizeof(*devi), M_DEVBUF, | |
| M_WAITOK | M_ZERO); | |
| strcpy(devi->name, desc->name); | |
| /* | /* |
| * Create an entry for the device under this slot. | * Increase the reference count on the socket, enabling power, if |
| * necessary. | |
| */ | */ |
| devi->running = 1; | pf->sc->sc_enabled_count++; |
| devi->slt = slt; | |
| bcopy(desc->misc, devi->misc, sizeof(desc->misc)); | if (pf->pf_flags & PFF_ENABLED) { |
| strcpy(devi->manufstr, desc->manufstr); | /* |
| strcpy(devi->versstr, desc->versstr); | * Don't do anything if we're already enabled. |
| devi->manufacturer = desc->manufacturer; | */ |
| devi->product = desc->product; | return (0); |
| devi->prodext = desc->prodext; | |
| resource_list_init(&devi->resources); | |
| child = device_add_child(pccarddev, devi->name, desc->unit); | |
| if (child == NULL) { | |
| if (desc->unit != -1) | |
| device_printf(pccarddev, | |
| "Unit %d failed for %s, try a different unit\n", | |
| desc->unit, devi->name); | |
| else | |
| device_printf(pccarddev, | |
| "No units available for %s. Impossible?\n", | |
| devi->name); | |
| return (EIO); | |
| } | |
| device_set_flags(child, desc->flags); | |
| device_set_ivars(child, devi); | |
| if (bootverbose) { | |
| device_printf(pccarddev, "Assigning %s:", | |
| device_get_nameunit(child)); | |
| if (desc->iobase) | |
| printf(" io 0x%x-0x%x", | |
| desc->iobase, desc->iobase + desc->iosize - 1); | |
| if (irq) | |
| printf(" irq %d", irq); | |
| if (desc->mem) | |
| printf(" mem 0x%lx-0x%lx", desc->mem, | |
| desc->mem + desc->memsize - 1); | |
| printf(" flags 0x%x\n", desc->flags); | |
| } | |
| err = bus_set_resource(child, SYS_RES_IOPORT, 0, desc->iobase, | |
| desc->iosize); | |
| if (err) | |
| goto err; | |
| if (irq) | |
| err = bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1); | |
| if (err) | |
| goto err; | |
| if (desc->memsize) { | |
| err = bus_set_resource(child, SYS_RES_MEMORY, 0, desc->mem, | |
| desc->memsize); | |
| if (err) | |
| goto err; | |
| } | } |
| err = device_probe_and_attach(child); | |
| /* | /* |
| * XXX We unwisely assume that the detach code won't run while the | * it's possible for different functions' CCRs to be in the same |
| * XXX the attach code is attaching. Someone should put some | * underlying page. Check for that. |
| * XXX interlock code. This can happen if probe/attach takes a while | |
| * XXX and the user ejects the card, which causes the detach | |
| * XXX function to be called. | |
| */ | */ |
| strncpy(desc->name, device_get_nameunit(child), sizeof(desc->name)); | STAILQ_FOREACH(tmp, &pf->sc->card.pf_head, pf_list) { |
| desc->name[sizeof(desc->name) - 1] = '\0'; | if ((tmp->pf_flags & PFF_ENABLED) && |
| err: | (pf->ccr_base >= (tmp->ccr_base - tmp->pf_ccr_offset)) && |
| if (err) | ((pf->ccr_base + PCCARD_CCR_SIZE) <= |
| device_delete_child(pccarddev, child); | (tmp->ccr_base - tmp->pf_ccr_offset + |
| return (err); | tmp->pf_ccr_realsize))) { |
| pf->pf_ccrt = tmp->pf_ccrt; | |
| pf->pf_ccrh = tmp->pf_ccrh; | |
| pf->pf_ccr_realsize = tmp->pf_ccr_realsize; | |
| /* | |
| * pf->pf_ccr_offset = (tmp->pf_ccr_offset - | |
| * tmp->ccr_base) + pf->ccr_base; | |
| */ | |
| /* pf->pf_ccr_offset = | |
| (tmp->pf_ccr_offset + pf->ccr_base) - | |
| tmp->ccr_base; */ | |
| pf->pf_ccr_window = tmp->pf_ccr_window; | |
| break; | |
| } | |
| } | |
| if (tmp == NULL) { | |
| pf->ccr_rid = 0; | |
| pf->ccr_res = bus_alloc_resource(dev, SYS_RES_MEMORY, | |
| &pf->ccr_rid, 0, ~0, 1 << 10, RF_ACTIVE); | |
| if (!pf->ccr_res) | |
| goto bad; | |
| DEVPRINTF((dev, "ccr_res == %lx-%lx, base=%x\n", | |
| rman_get_start(pf->ccr_res), rman_get_end(pf->ccr_res), | |
| pf->ccr_base)); | |
| CARD_SET_RES_FLAGS(device_get_parent(dev), dev, SYS_RES_MEMORY, | |
| pf->ccr_rid, PCCARD_A_MEM_ATTR); | |
| CARD_SET_MEMORY_OFFSET(device_get_parent(dev), dev, | |
| pf->ccr_rid, pf->ccr_base, &pf->pf_ccr_offset); | |
| pf->pf_ccrt = rman_get_bustag(pf->ccr_res); | |
| pf->pf_ccrh = rman_get_bushandle(pf->ccr_res); | |
| pf->pf_ccr_realsize = 1; | |
| } | |
| reg = (pf->cfe->number & PCCARD_CCR_OPTION_CFINDEX); | |
| reg |= PCCARD_CCR_OPTION_LEVIREQ; | |
| if (pccard_mfc(pf->sc)) { | |
| reg |= (PCCARD_CCR_OPTION_FUNC_ENABLE | | |
| PCCARD_CCR_OPTION_ADDR_DECODE); | |
| /* PCCARD_CCR_OPTION_IRQ_ENABLE set elsewhere as needed */ | |
| } | |
| pccard_ccr_write(pf, PCCARD_CCR_OPTION, reg); | |
| reg = 0; | |
| if ((pf->cfe->flags & PCCARD_CFE_IO16) == 0) | |
| reg |= PCCARD_CCR_STATUS_IOIS8; | |
| if (pf->cfe->flags & PCCARD_CFE_AUDIO) | |
| reg |= PCCARD_CCR_STATUS_AUDIO; | |
| pccard_ccr_write(pf, PCCARD_CCR_STATUS, reg); | |
| pccard_ccr_write(pf, PCCARD_CCR_SOCKETCOPY, 0); | |
| if (pccard_mfc(pf->sc)) { | |
| long tmp, iosize; | |
| tmp = pf->pf_mfc_iomax - pf->pf_mfc_iobase; | |
| /* round up to nearest (2^n)-1 */ | |
| for (iosize = 1; iosize < tmp; iosize <<= 1) | |
| ; | |
| iosize--; | |
| pccard_ccr_write(pf, PCCARD_CCR_IOBASE0, | |
| pf->pf_mfc_iobase & 0xff); | |
| pccard_ccr_write(pf, PCCARD_CCR_IOBASE1, | |
| (pf->pf_mfc_iobase >> 8) & 0xff); | |
| pccard_ccr_write(pf, PCCARD_CCR_IOBASE2, 0); | |
| pccard_ccr_write(pf, PCCARD_CCR_IOBASE3, 0); | |
| pccard_ccr_write(pf, PCCARD_CCR_IOSIZE, iosize); | |
| } | |
| #ifdef PCCARDDEBUG | |
| if (pccard_debug) { | |
| STAILQ_FOREACH(tmp, &pf->sc->card.pf_head, pf_list) { | |
| device_printf(tmp->sc->dev, | |
| "function %d CCR at %d offset %x: " | |
| "%x %x %x %x, %x %x %x %x, %x\n", | |
| tmp->number, tmp->pf_ccr_window, | |
| tmp->pf_ccr_offset, | |
| pccard_ccr_read(tmp, 0x00), | |
| pccard_ccr_read(tmp, 0x02), | |
| pccard_ccr_read(tmp, 0x04), | |
| pccard_ccr_read(tmp, 0x06), | |
| pccard_ccr_read(tmp, 0x0A), | |
| pccard_ccr_read(tmp, 0x0C), | |
| pccard_ccr_read(tmp, 0x0E), | |
| pccard_ccr_read(tmp, 0x10), | |
| pccard_ccr_read(tmp, 0x12)); | |
| } | |
| } | |
| #endif | |
| pf->pf_flags |= PFF_ENABLED; | |
| return (0); | |
| bad: | |
| /* | |
| * Decrement the reference count, and power down the socket, if | |
| * necessary. | |
| */ | |
| pf->sc->sc_enabled_count--; | |
| DEVPRINTF((dev, "bad --enabled_count = %d\n", pf->sc->sc_enabled_count)); | |
| return (1); | |
| } | } |
| /* | /* Disable PCCARD function. */ |
| * card insert routine - Called from a timeout to debounce | |
| * insertion events. | |
| */ | |
| static void | static void |
| inserted(void *arg) | pccard_function_disable(struct pccard_function *pf) |
| { | { |
| struct slot *slt = arg; | struct pccard_function *tmp; |
| device_t dev = pf->sc->dev; | |
| if (pf->cfe == NULL) | |
| panic("pccard_function_disable: function not initialized"); | |
| if ((pf->pf_flags & PFF_ENABLED) == 0) { | |
| /* | |
| * Don't do anything if we're already disabled. | |
| */ | |
| return; | |
| } | |
| if (pf->intr_handler != NULL) { | |
| struct pccard_ivar *devi = PCCARD_IVAR(pf->dev); | |
| struct resource_list_entry *rle = | |
| resource_list_find(&devi->resources, SYS_RES_IRQ, 0); | |
| BUS_TEARDOWN_INTR(dev, pf->dev, rle->res, | |
| pf->intr_handler_cookie); | |
| } | |
| slt->state = filled; | |
| /* | /* |
| * Disable any pending timeouts for this slot, and explicitly | * it's possible for different functions' CCRs to be in the same |
| * power it off right now. Then, re-enable the power using | * underlying page. Check for that. Note we mark us as disabled |
| * the (possibly new) power settings. | * first to avoid matching ourself. |
| */ | */ |
| untimeout(power_off_slot, (caddr_t)slt, slt->poff_ch); | |
| power_off_slot(slt); | pf->pf_flags &= ~PFF_ENABLED; |
| STAILQ_FOREACH(tmp, &pf->sc->card.pf_head, pf_list) { | |
| if ((tmp->pf_flags & PFF_ENABLED) && | |
| (pf->ccr_base >= (tmp->ccr_base - tmp->pf_ccr_offset)) && | |
| ((pf->ccr_base + PCCARD_CCR_SIZE) <= | |
| (tmp->ccr_base - tmp->pf_ccr_offset + | |
| tmp->pf_ccr_realsize))) | |
| break; | |
| } | |
| /* Not used by anyone else; unmap the CCR. */ | |
| if (tmp == NULL) { | |
| bus_release_resource(dev, SYS_RES_MEMORY, pf->ccr_rid, | |
| pf->ccr_res); | |
| pf->ccr_res = NULL; | |
| } | |
| /* | /* |
| * Enable 5V to the card so that the CIS can be read. Well, | * Decrement the reference count, and power down the socket, if |
| * enable the most natural voltage so that the CIS can be read. | * necessary. |
| */ | */ |
| slt->pwr.vcc = -1; | pf->sc->sc_enabled_count--; |
| slt->pwr.vpp = -1; | |
| slt->ctrl->power(slt); | |
| printf("pccard: card inserted, slot %d\n", slt->slotnum); | |
| pccard_insert_beep(); | |
| slt->ctrl->reset(slt); | |
| } | } |
| /* | /* |
| * Card event callback. Called at splhigh to prevent | * simulate the old "probe" routine. In the new world order, the driver |
| * device interrupts from interceding. | * needs to grab devices while in the old they were assigned to the device by |
| * the pccardd process. These symbols are exported to the upper layers. | |
| */ | */ |
| void | static int |
| pccard_event(struct slot *slt, enum card_event event) | pccard_compat_do_probe(device_t bus, device_t dev) |
| { | { |
| if (slt->insert_seq) { | return (CARD_COMPAT_MATCH(dev)); |
| slt->insert_seq = 0; | } |
| untimeout(inserted, (void *)slt, slt->insert_ch); | |
| } | |
| switch(event) { | static int |
| case card_removed: | pccard_compat_do_attach(device_t bus, device_t dev) |
| case card_deactivated: | { |
| if (slt->state == filled || slt->state == inactive) { | int err; |
| if (event == card_removed) | |
| slt->state = empty; | err = CARD_COMPAT_PROBE(dev); |
| else | if (err == 0) |
| slt->state = inactive; | err = CARD_COMPAT_ATTACH(dev); |
| disable_slot_to(slt); | return (err); |
| } | |
| break; | |
| case card_inserted: | |
| slt->insert_seq = 1; | |
| slt->insert_ch = timeout(inserted, (void *)slt, hz/4); | |
| break; | |
| } | |
| } | } |
| /* | #define PCCARD_NPORT 2 |
| * Device driver interface. | #define PCCARD_NMEM 5 |
| */ | #define PCCARD_NIRQ 1 |
| static int | #define PCCARD_NDRQ 0 |
| crdopen(dev_t dev, int oflags, int devtype, d_thread_t *td) | |
| static int | |
| pccard_add_children(device_t dev, int busno) | |
| { | |
| /* Call parent to scan for any current children */ | |
| return (0); | |
| } | |
| static int | |
| pccard_probe(device_t dev) | |
| { | |
| device_set_desc(dev, "16-bit PCCard bus"); | |
| return (pccard_add_children(dev, device_get_unit(dev))); | |
| } | |
| static int | |
| pccard_attach(device_t dev) | |
| { | { |
| struct slot *slt = PCCARD_DEV2SOFTC(dev); | struct pccard_softc *sc = PCCARD_SOFTC(dev); |
| if (slt == NULL) | sc->dev = dev; |
| return (ENXIO); | sc->sc_enabled_count = 0; |
| if (slt->rwmem == 0) | return (bus_generic_attach(dev)); |
| slt->rwmem = MDF_ATTR; | } |
| static int | |
| pccard_detach(device_t dev) | |
| { | |
| pccard_detach_card(dev); | |
| return 0; | |
| } | |
| static int | |
| pccard_suspend(device_t self) | |
| { | |
| pccard_detach_card(self); | |
| return (0); | return (0); |
| } | } |
| /* | static int |
| * Close doesn't de-allocate any resources, since | pccard_resume(device_t self) |
| * slots may be assigned to drivers already. | |
| */ | |
| static int | |
| crdclose(dev_t dev, int fflag, int devtype, d_thread_t *td) | |
| { | { |
| return (0); | return (0); |
| } | } |
| /* | static void |
| * read interface. Map memory at lseek offset, | pccard_print_resources(struct resource_list *rl, const char *name, int type, |
| * then transfer to user space. | int count, const char *format) |
| */ | |
| static int | |
| crdread(dev_t dev, struct uio *uio, int ioflag) | |
| { | { |
| struct slot *slt = PCCARD_DEV2SOFTC(dev); | struct resource_list_entry *rle; |
| struct mem_desc *mp, oldmap; | int printed; |
| unsigned char *p; | int i; |
| unsigned int offs; | |
| int error = 0, win, count; | printed = 0; |
| for (i = 0; i < count; i++) { | |
| if (slt == 0 || slt->state != filled) | rle = resource_list_find(rl, type, i); |
| return (ENXIO); | if (rle != NULL) { |
| if (pccard_mem == 0) | if (printed == 0) |
| return (ENOMEM); | printf(" %s ", name); |
| for (win = slt->ctrl->maxmem - 1; win >= 0; win--) | else if (printed > 0) |
| if ((slt->mem[win].flags & MDF_ACTIVE) == 0) | printf(","); |
| break; | printed++; |
| if (win < 0) | printf(format, rle->start); |
| return (EBUSY); | if (rle->count > 1) { |
| mp = &slt->mem[win]; | printf("-"); |
| oldmap = *mp; | printf(format, rle->start + rle->count - 1); |
| mp->flags = slt->rwmem | MDF_ACTIVE; | } |
| while (uio->uio_resid && error == 0) { | } else if (i > 3) { |
| mp->card = uio->uio_offset; | /* check the first few regardless */ |
| mp->size = PCCARD_MEMSIZE; | |
| mp->start = (caddr_t)(void *)(uintptr_t)pccard_mem; | |
| if ((error = slt->ctrl->mapmem(slt, win)) != 0) | |
| break; | 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); | |
| } | } |
| /* | static int |
| * crdwrite - Write data to card memory. | pccard_print_child(device_t dev, device_t child) |
| * 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_DEV2SOFTC(dev); | struct pccard_ivar *devi = PCCARD_IVAR(child); |
| struct mem_desc *mp, oldmap; | struct resource_list *rl = &devi->resources; |
| unsigned char *p; | int retval = 0; |
| unsigned int offs; | |
| int error = 0, win, count; | retval += bus_print_child_header(dev, child); |
| retval += printf(" at"); | |
| if (slt == 0 || slt->state != filled) | |
| return (ENXIO); | if (devi != NULL) { |
| if (pccard_mem == 0) | pccard_print_resources(rl, "port", SYS_RES_IOPORT, |
| return (ENOMEM); | PCCARD_NPORT, "%#lx"); |
| for (win = slt->ctrl->maxmem - 1; win >= 0; win--) | pccard_print_resources(rl, "iomem", SYS_RES_MEMORY, |
| if ((slt->mem[win].flags & MDF_ACTIVE) == 0) | PCCARD_NMEM, "%#lx"); |
| break; | pccard_print_resources(rl, "irq", SYS_RES_IRQ, PCCARD_NIRQ, |
| if (win < 0) | "%ld"); |
| return (EBUSY); | pccard_print_resources(rl, "drq", SYS_RES_DRQ, PCCARD_NDRQ, |
| mp = &slt->mem[win]; | "%ld"); |
| oldmap = *mp; | retval += printf(" function %d config %d", devi->fcn->number, |
| mp->flags = slt->rwmem | MDF_ACTIVE; | devi->fcn->cfe->number); |
| 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); | retval += bus_print_child_footer(dev, child); |
| return (retval); | |
| } | } |
| /* | static int |
| * ioctl calls - allows setting/getting of memory and I/O | pccard_set_resource(device_t dev, device_t child, int type, int rid, |
| * descriptors, and assignment of drivers. | u_long start, u_long count) |
| */ | |
| static int | |
| crdioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, d_thread_t *td) | |
| { | { |
| u_int32_t addr; | struct pccard_ivar *devi = PCCARD_IVAR(child); |
| int err; | struct resource_list *rl = &devi->resources; |
| struct io_desc *ip; | |
| struct mem_desc *mp; | |
| device_t pccarddev; | |
| int pwval; | |
| int s; | |
| struct slot *slt = PCCARD_DEV2SOFTC(dev); | |
| if (slt == 0 && cmd != PIOCRWMEM) | if (type != SYS_RES_IOPORT && type != SYS_RES_MEMORY |
| return (ENXIO); | && type != SYS_RES_IRQ && type != SYS_RES_DRQ) |
| return (EINVAL); | |
| if (rid < 0) | |
| return (EINVAL); | |
| if (type == SYS_RES_IOPORT && rid >= PCCARD_NPORT) | |
| return (EINVAL); | |
| if (type == SYS_RES_MEMORY && rid >= PCCARD_NMEM) | |
| return (EINVAL); | |
| if (type == SYS_RES_IRQ && rid >= PCCARD_NIRQ) | |
| return (EINVAL); | |
| if (type == SYS_RES_DRQ && rid >= PCCARD_NDRQ) | |
| return (EINVAL); | |
| resource_list_add(rl, type, rid, start, start + count - 1, count); | |
| if (NULL != resource_list_alloc(rl, device_get_parent(dev), dev, | |
| type, &rid, start, start + count - 1, count, 0)) | |
| return 0; | |
| else | |
| return ENOMEM; | |
| } | |
| static int | |
| pccard_get_resource(device_t dev, device_t child, int type, int rid, | |
| u_long *startp, u_long *countp) | |
| { | |
| struct pccard_ivar *devi = PCCARD_IVAR(child); | |
| struct resource_list *rl = &devi->resources; | |
| struct resource_list_entry *rle; | |
| rle = resource_list_find(rl, type, rid); | |
| if (rle == NULL) | |
| return (ENOENT); | |
| if (startp != NULL) | |
| *startp = rle->start; | |
| if (countp != NULL) | |
| *countp = rle->count; | |
| KKASSERT(td->td_proc != NULL); | return (0); |
| } | |
| switch(cmd) { | static void |
| pccard_delete_resource(device_t dev, device_t child, int type, int rid) | |
| { | |
| struct pccard_ivar *devi = PCCARD_IVAR(child); | |
| struct resource_list *rl = &devi->resources; | |
| resource_list_delete(rl, type, rid); | |
| } | |
| static int | |
| pccard_set_res_flags(device_t dev, device_t child, int type, int rid, | |
| u_int32_t flags) | |
| { | |
| return (CARD_SET_RES_FLAGS(device_get_parent(dev), child, type, | |
| rid, flags)); | |
| } | |
| static int | |
| pccard_set_memory_offset(device_t dev, device_t child, int rid, | |
| u_int32_t offset, u_int32_t *deltap) | |
| { | |
| return (CARD_SET_MEMORY_OFFSET(device_get_parent(dev), child, rid, | |
| offset, deltap)); | |
| } | |
| static void | |
| pccard_probe_nomatch(device_t bus, device_t child) | |
| { | |
| struct pccard_ivar *devi = PCCARD_IVAR(child); | |
| struct pccard_function *func = devi->fcn; | |
| struct pccard_softc *sc = PCCARD_SOFTC(bus); | |
| device_printf(bus, "<unknown card>"); | |
| printf(" (manufacturer=0x%04x, product=0x%04x) at function %d\n", | |
| sc->card.manufacturer, sc->card.product, func->number); | |
| device_printf(bus, " CIS info: %s, %s, %s\n", sc->card.cis1_info[0], | |
| sc->card.cis1_info[1], sc->card.cis1_info[2]); | |
| return; | |
| } | |
| static int | |
| pccard_child_location_str(device_t bus, device_t child, char *buf, | |
| size_t buflen) | |
| { | |
| struct pccard_ivar *devi = PCCARD_IVAR(child); | |
| struct pccard_function *func = devi->fcn; | |
| snprintf(buf, buflen, "function=%d", func->number); | |
| return (0); | |
| } | |
| static int | |
| pccard_child_pnpinfo_str(device_t bus, device_t child, char *buf, | |
| size_t buflen) | |
| { | |
| struct pccard_ivar *devi = PCCARD_IVAR(child); | |
| struct pccard_function *func = devi->fcn; | |
| struct pccard_softc *sc = PCCARD_SOFTC(bus); | |
| snprintf(buf, buflen, "manufacturer=0x%04x product=0x%04x " | |
| "cisvendor=\"%s\" cisproduct=\"%s\" function_type=%d", | |
| sc->card.manufacturer, sc->card.product, sc->card.cis1_info[0], | |
| sc->card.cis1_info[1], func->function); | |
| return (0); | |
| } | |
| static int | |
| pccard_read_ivar(device_t bus, device_t child, int which, u_char *result) | |
| { | |
| struct pccard_ivar *devi = PCCARD_IVAR(child); | |
| struct pccard_function *func = devi->fcn; | |
| struct pccard_softc *sc = PCCARD_SOFTC(bus); | |
| switch (which) { | |
| default: | default: |
| if (slt->ctrl->ioctl) | case PCCARD_IVAR_ETHADDR: |
| return (slt->ctrl->ioctl(slt, cmd, data)); | bcopy(func->pf_funce_lan_nid, result, ETHER_ADDR_LEN); |
| return (ENOTTY); | |
| /* | |
| * 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 = 0; | |
| break; | break; |
| /* | case PCCARD_IVAR_VENDOR: |
| * Get memory context. | *(u_int32_t *) result = sc->card.manufacturer; |
| */ | |
| 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; | break; |
| /* | case PCCARD_IVAR_PRODUCT: |
| * Set memory context. If context already active, then unmap it. | *(u_int32_t *) result = sc->card.product; |
| * 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(td)) | |
| 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; | break; |
| /* | case PCCARD_IVAR_PRODEXT: |
| * Set I/O port context. | *(u_int16_t *) result = sc->card.prodext; |
| */ | |
| case PIOCSIO: | |
| if (suser(td)) | |
| 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); | |
| /* XXX Don't actually map */ | |
| return (0); | |
| break; | break; |
| /* | case PCCARD_IVAR_FUNCTION: |
| * Set memory window flags for read/write interface. | *(u_int32_t *) result = func->function; |
| */ | |
| case PIOCRWFLAG: | |
| slt->rwmem = *(int *)data; | |
| break; | break; |
| /* | case PCCARD_IVAR_FUNCTION_NUMBER: |
| * Set the memory window to be used for the read/write interface. | if (!func) { |
| */ | device_printf(bus, "No function number, bug!\n"); |
| case PIOCRWMEM: | return (ENOENT); |
| if (*(unsigned long *)data == 0) { | |
| *(unsigned long *)data = pccard_mem; | |
| break; | |
| } | } |
| if (suser(td)) | *(u_int32_t *) result = func->number; |
| return (EPERM); | |
| /* | |
| * Validate the memory by checking it against the I/O | |
| * memory range. It must also start on an aligned block size. | |
| */ | |
| if (*(unsigned long *)data & (PCCARD_MEMSIZE-1)) | |
| return (EINVAL); | |
| pccarddev = PCCARD_DEV2SOFTC(dev)->dev; | |
| pccard_mem_rid = 0; | |
| addr = *(unsigned long *)data; | |
| if (pccard_mem_res) | |
| bus_release_resource(pccarddev, SYS_RES_MEMORY, | |
| pccard_mem_rid, pccard_mem_res); | |
| pccard_mem_res = bus_alloc_resource(pccarddev, SYS_RES_MEMORY, | |
| &pccard_mem_rid, addr, addr, PCCARD_MEMSIZE, | |
| RF_ACTIVE | rman_make_alignment_flags(PCCARD_MEMSIZE)); | |
| if (pccard_mem_res == NULL) | |
| return (EINVAL); | |
| pccard_mem = rman_get_start(pccard_mem_res); | |
| pccard_kmem = rman_get_virtual(pccard_mem_res); | |
| break; | break; |
| /* | case PCCARD_IVAR_VENDOR_STR: |
| * Set power values. | *(char **) result = sc->card.cis1_info[0]; |
| */ | |
| case PIOCSPOW: | |
| slt->pwr = *(struct power *)data; | |
| return (slt->ctrl->power(slt)); | |
| /* | |
| * Allocate a driver to this slot. | |
| */ | |
| case PIOCSDRV: | |
| if (suser(td)) | |
| return (EPERM); | |
| err = allocate_driver(slt, (struct dev_desc *)data); | |
| if (!err) | |
| pccard_success_beep(); | |
| else | |
| pccard_failure_beep(); | |
| return (err); | |
| /* | |
| * Virtual removal/insertion | |
| */ | |
| case PIOCSVIR: | |
| pwval = *(int *)data; | |
| if (!pwval) { | |
| if (slt->state != filled) | |
| return (EINVAL); | |
| pccard_event(slt, card_deactivated); | |
| } else { | |
| if (slt->state != empty && slt->state != inactive) | |
| return (EINVAL); | |
| pccard_event(slt, card_inserted); | |
| } | |
| break; | break; |
| case PIOCSBEEP: | case PCCARD_IVAR_PRODUCT_STR: |
| if (pccard_beep_select(*(int *)data)) { | *(char **) result = sc->card.cis1_info[1]; |
| return (EINVAL); | break; |
| } | case PCCARD_IVAR_CIS3_STR: |
| *(char **) result = sc->card.cis1_info[2]; | |
| break; | |
| case PCCARD_IVAR_CIS4_STR: | |
| *(char **) result = sc->card.cis1_info[3]; | |
| break; | break; |
| } | } |
| return (0); | return (0); |
| } | } |
| /* | static void |
| * poll - Poll on exceptions will return true | pccard_driver_added(device_t dev, driver_t *driver) |
| * when a change in card status occurs. | |
| */ | |
| static int | |
| crdpoll(dev_t dev, int events, d_thread_t *td) | |
| { | { |
| int revents = 0; | struct pccard_softc *sc = PCCARD_SOFTC(dev); |
| int s; | struct pccard_function *pf; |
| struct slot *slt = PCCARD_DEV2SOFTC(dev); | device_t child; |
| if (events & (POLLIN | POLLRDNORM)) | STAILQ_FOREACH(pf, &sc->card.pf_head, pf_list) { |
| revents |= events & (POLLIN | POLLRDNORM); | if (STAILQ_EMPTY(&pf->cfe_head)) |
| continue; | |
| child = pf->dev; | |
| if (device_get_state(child) != DS_NOTPRESENT) | |
| continue; | |
| if (pccard_function_enable(pf) == 0 && | |
| device_probe_and_attach(child) == 0) { | |
| DEVPRINTF((sc->dev, "function %d CCR at %d " | |
| "offset %x: %x %x %x %x, %x %x %x %x, %x\n", | |
| pf->number, pf->pf_ccr_window, pf->pf_ccr_offset, | |
| pccard_ccr_read(pf, 0x00), | |
| pccard_ccr_read(pf, 0x02), pccard_ccr_read(pf, 0x04), | |
| pccard_ccr_read(pf, 0x06), pccard_ccr_read(pf, 0x0A), | |
| pccard_ccr_read(pf, 0x0C), pccard_ccr_read(pf, 0x0E), | |
| pccard_ccr_read(pf, 0x10), pccard_ccr_read(pf, 0x12))); | |
| } else { | |
| if (pf->cfe != NULL) | |
| pccard_function_disable(pf); | |
| } | |
| } | |
| return; | |
| } | |
| if (events & (POLLOUT | POLLWRNORM)) | static struct resource * |
| revents |= events & (POLLIN | POLLRDNORM); | pccard_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 pccard_ivar *dinfo; | |
| struct resource_list_entry *rle = 0; | |
| int passthrough = (device_get_parent(child) != dev); | |
| struct resource *r = NULL; | |
| if (passthrough) { | |
| return (BUS_ALLOC_RESOURCE(device_get_parent(dev), child, | |
| type, rid, start, end, count, flags)); | |
| } | |
| s = splhigh(); | dinfo = device_get_ivars(child); |
| /* | rle = resource_list_find(&dinfo->resources, type, *rid); |
| * select for exception - card event. | |
| */ | |
| if (events & POLLRDBAND) | |
| if (slt == 0 || slt->laststate != slt->state) | |
| revents |= POLLRDBAND; | |
| if (revents == 0) | if (rle == NULL) |
| selrecord(td, &slt->selp); | return (NULL); /* no resource of that type/rid */ |
| splx(s); | if (rle->res == NULL) { |
| return (revents); | switch(type) { |
| case SYS_RES_IOPORT: | |
| r = bus_alloc_resource(dev, type, rid, start, end, | |
| count, rman_make_alignment_flags(count)); | |
| if (r == NULL) | |
| goto bad; | |
| resource_list_add(&dinfo->resources, type, *rid, | |
| rman_get_start(r), rman_get_end(r), count); | |
| rle = resource_list_find(&dinfo->resources, type, *rid); | |
| if (!rle) | |
| goto bad; | |
| rle->res = r; | |
| break; | |
| case SYS_RES_MEMORY: | |
| break; | |
| case SYS_RES_IRQ: | |
| break; | |
| } | |
| return (rle->res); | |
| } | |
| if (rle->res->r_dev != dev) | |
| return (NULL); | |
| bus_release_resource(dev, type, *rid, rle->res); | |
| rle->res = NULL; | |
| switch(type) { | |
| case SYS_RES_IOPORT: | |
| case SYS_RES_MEMORY: | |
| if (!(flags & RF_ALIGNMENT_MASK)) | |
| flags |= rman_make_alignment_flags(rle->count); | |
| break; | |
| case SYS_RES_IRQ: | |
| flags |= RF_SHAREABLE; | |
| break; | |
| } | |
| rle->res = resource_list_alloc(&dinfo->resources, dev, child, | |
| type, rid, rle->start, rle->end, rle->count, flags); | |
| return (rle->res); | |
| bad:; | |
| device_printf(dev, "WARNING: Resource not reserved by pccard\n"); | |
| return (NULL); | |
| } | } |
| /* | static int |
| * APM hooks for suspending and resuming. | pccard_release_resource(device_t dev, device_t child, int type, int rid, |
| */ | struct resource *r) |
| int | |
| pccard_suspend(device_t dev) | |
| { | { |
| struct slot *slt = PCCARD_DEVICE2SOFTC(dev); | struct pccard_ivar *dinfo; |
| int passthrough = (device_get_parent(child) != dev); | |
| struct resource_list_entry *rle = 0; | |
| int ret; | |
| int flags; | |
| /* This code stolen from pccard_event:card_removed */ | if (passthrough) |
| if (slt->state == filled) { | return BUS_RELEASE_RESOURCE(device_get_parent(dev), child, |
| int s = splhigh(); /* nop on current */ | type, rid, r); |
| disable_slot(slt); | |
| slt->laststate = suspend; /* for pccardd */ | dinfo = device_get_ivars(child); |
| slt->state = empty; | |
| splx(s); | rle = resource_list_find(&dinfo->resources, type, rid); |
| printf("pccard: card disabled, slot %d\n", slt->slotnum); | |
| if (!rle) { | |
| device_printf(dev, "Allocated resource not found, " | |
| "%d %x %lx %lx\n", | |
| type, rid, rman_get_start(r), rman_get_size(r)); | |
| return ENOENT; | |
| } | } |
| if (!rle->res) { | |
| device_printf(dev, "Allocated resource not recorded\n"); | |
| return ENOENT; | |
| } | |
| ret = BUS_RELEASE_RESOURCE(device_get_parent(dev), child, | |
| type, rid, r); | |
| switch(type) { | |
| case SYS_RES_IOPORT: | |
| case SYS_RES_MEMORY: | |
| flags = rman_make_alignment_flags(rle->count); | |
| break; | |
| case SYS_RES_IRQ: | |
| flags = RF_SHAREABLE; | |
| break; | |
| default: | |
| flags = 0; | |
| } | |
| rle->res = bus_alloc_resource(dev, type, &rid, | |
| rle->start, rle->end, rle->count, flags); | |
| if (rle->res == NULL) | |
| device_printf(dev, "release_resource: " | |
| "unable to reaquire resource\n"); | |
| return ret; | |
| } | |
| static void | |
| pccard_child_detached(device_t parent, device_t dev) | |
| { | |
| struct pccard_ivar *ivar = PCCARD_IVAR(dev); | |
| struct pccard_function *pf = ivar->fcn; | |
| pccard_function_disable(pf); | |
| } | |
| static void | |
| pccard_intr(void *arg) | |
| { | |
| struct pccard_function *pf = (struct pccard_function*) arg; | |
| int reg; | |
| int doisr = 1; | |
| /* | /* |
| * Disable any pending timeouts for this slot since we're | * MFC cards know if they interrupted, so we have to ack the |
| * powering it down/disabling now. | * interrupt and call the ISR. Non-MFC cards don't have these |
| * bits, so they always get called. Many non-MFC cards have | |
| * this bit set always upon read, but some do not. | |
| * | |
| * We always ack the interrupt, even if there's no ISR | |
| * for the card. This is done on the theory that acking | |
| * the interrupt will pacify the card enough to keep an | |
| * interrupt storm from happening. Of course this won't | |
| * help in the non-MFC case. | |
| */ | */ |
| untimeout(power_off_slot, (caddr_t)slt, slt->poff_ch); | if (pccard_mfc(pf->sc)) { |
| slt->ctrl->disable(slt); | reg = pccard_ccr_read(pf, PCCARD_CCR_STATUS); |
| if (reg & PCCARD_CCR_STATUS_INTR) | |
| pccard_ccr_write(pf, PCCARD_CCR_STATUS, | |
| reg & ~PCCARD_CCR_STATUS_INTR); | |
| else | |
| doisr = 0; | |
| } | |
| if (pf->intr_handler != NULL && doisr) | |
| pf->intr_handler(pf->intr_handler_arg); | |
| } | |
| static int | |
| pccard_setup_intr(device_t dev, device_t child, struct resource *irq, | |
| int flags, driver_intr_t *intr, void *arg, void **cookiep) | |
| { | |
| struct pccard_softc *sc = PCCARD_SOFTC(dev); | |
| struct pccard_ivar *ivar = PCCARD_IVAR(child); | |
| struct pccard_function *func = ivar->fcn; | |
| int err; | |
| if (func->intr_handler != NULL) | |
| panic("Only one interrupt handler per function allowed"); | |
| err = bus_generic_setup_intr(dev, child, irq, flags, pccard_intr, | |
| func, cookiep); | |
| if (err != 0) | |
| return (err); | |
| func->intr_handler = intr; | |
| func->intr_handler_arg = arg; | |
| func->intr_handler_cookie = *cookiep; | |
| if (pccard_mfc(sc)) { | |
| pccard_ccr_write(func, PCCARD_CCR_OPTION, | |
| pccard_ccr_read(func, PCCARD_CCR_OPTION) | | |
| PCCARD_CCR_OPTION_IREQ_ENABLE); | |
| } | |
| return (0); | return (0); |
| } | } |
| int | static int |
| pccard_resume(device_t dev) | pccard_teardown_intr(device_t dev, device_t child, struct resource *r, |
| void *cookie) | |
| { | { |
| struct slot *slt = PCCARD_DEVICE2SOFTC(dev); | struct pccard_softc *sc = PCCARD_SOFTC(dev); |
| struct pccard_ivar *ivar = PCCARD_IVAR(child); | |
| struct pccard_function *func = ivar->fcn; | |
| int ret; | |
| slt->ctrl->resume(slt); | if (pccard_mfc(sc)) { |
| return (0); | pccard_ccr_write(func, PCCARD_CCR_OPTION, |
| pccard_ccr_read(func, PCCARD_CCR_OPTION) & | |
| ~PCCARD_CCR_OPTION_IREQ_ENABLE); | |
| } | |
| ret = bus_generic_teardown_intr(dev, child, r, cookie); | |
| if (ret == 0) { | |
| func->intr_handler = NULL; | |
| func->intr_handler_arg = NULL; | |
| func->intr_handler_cookie = NULL; | |
| } | |
| return (ret); | |
| } | } |
| static device_method_t pccard_methods[] = { | |
| /* Device interface */ | |
| DEVMETHOD(device_probe, pccard_probe), | |
| DEVMETHOD(device_attach, pccard_attach), | |
| DEVMETHOD(device_detach, pccard_detach), | |
| DEVMETHOD(device_shutdown, bus_generic_shutdown), | |
| DEVMETHOD(device_suspend, pccard_suspend), | |
| DEVMETHOD(device_resume, pccard_resume), | |
| /* Bus interface */ | |
| DEVMETHOD(bus_print_child, pccard_print_child), | |
| DEVMETHOD(bus_driver_added, pccard_driver_added), | |
| DEVMETHOD(bus_child_detached, pccard_child_detached), | |
| DEVMETHOD(bus_alloc_resource, pccard_alloc_resource), | |
| DEVMETHOD(bus_release_resource, pccard_release_resource), | |
| DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), | |
| DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), | |
| DEVMETHOD(bus_setup_intr, pccard_setup_intr), | |
| DEVMETHOD(bus_teardown_intr, pccard_teardown_intr), | |
| DEVMETHOD(bus_set_resource, pccard_set_resource), | |
| DEVMETHOD(bus_get_resource, pccard_get_resource), | |
| DEVMETHOD(bus_delete_resource, pccard_delete_resource), | |
| DEVMETHOD(bus_probe_nomatch, pccard_probe_nomatch), | |
| DEVMETHOD(bus_read_ivar, pccard_read_ivar), | |
| DEVMETHOD(bus_child_pnpinfo_str, pccard_child_pnpinfo_str), | |
| DEVMETHOD(bus_child_location_str, pccard_child_location_str), | |
| /* Card Interface */ | |
| DEVMETHOD(card_set_res_flags, pccard_set_res_flags), | |
| DEVMETHOD(card_set_memory_offset, pccard_set_memory_offset), | |
| DEVMETHOD(card_get_type, pccard_card_gettype), | |
| DEVMETHOD(card_attach_card, pccard_attach_card), | |
| DEVMETHOD(card_detach_card, pccard_detach_card), | |
| DEVMETHOD(card_compat_do_probe, pccard_compat_do_probe), | |
| DEVMETHOD(card_compat_do_attach, pccard_compat_do_attach), | |
| DEVMETHOD(card_do_product_lookup, pccard_do_product_lookup), | |
| { 0, 0 } | |
| }; | |
| static driver_t pccard_driver = { | |
| "pccard", | |
| pccard_methods, | |
| sizeof(struct pccard_softc) | |
| }; | |
| devclass_t pccard_devclass; | |
| /* Maybe we need to have a slot device? */ | |
| DRIVER_MODULE(pccard, pcic, pccard_driver, pccard_devclass, 0, 0); | |
| DRIVER_MODULE(pccard, pc98pcic, pccard_driver, pccard_devclass, 0, 0); | |
| DRIVER_MODULE(pccard, cbb, pccard_driver, pccard_devclass, 0, 0); | |
| DRIVER_MODULE(pccard, tcic, pccard_driver, pccard_devclass, 0, 0); | |
| MODULE_VERSION(pccard, 1); | |
| /*MODULE_DEPEND(pccard, pcic, 1, 1, 1);*/ |