binutils214 stage 2/4.
[dragonfly.git] / sys / bus / pccard / pccard.c
1 /*
2  *      pccard.c - Interface code for PC-CARD controllers.
3  *
4  *      June 1995, Andrew McRae (andrew@mega.com.au)
5  *-------------------------------------------------------------------------
6  *
7  * Copyright (c) 2001 M. Warner Losh.  All rights reserved.
8  * Copyright (c) 1995 Andrew McRae.  All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. The name of the author may not be used to endorse or promote products
19  *    derived from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  *
32  * $FreeBSD: src/sys/pccard/pccard.c,v 1.106.2.15 2003/02/26 18:42:00 imp Exp $
33  * $DragonFly: src/sys/bus/pccard/pccard.c,v 1.7 2003/08/07 21:16:46 dillon Exp $
34  */
35
36 #include <sys/param.h>
37 #include <sys/types.h>
38 #include <sys/systm.h>
39 #include <sys/kernel.h>
40 #include <sys/malloc.h>
41 #include <sys/sysctl.h>
42 #include <sys/conf.h>
43 #include <sys/uio.h>
44 #include <sys/poll.h>
45 #include <sys/bus.h>
46 #include <sys/proc.h>
47 #include <machine/bus.h>
48
49 #include "cardinfo.h"
50 #include "driver.h"
51 #include "slot.h"
52 #include "pccard_nbk.h"
53
54 #include <machine/md_var.h>
55
56 static int              allocate_driver(struct slot *, struct dev_desc *);
57 static void             inserted(void *);
58 static void             disable_slot(struct slot *);
59 static void             disable_slot_to(struct slot *);
60 static void             power_off_slot(void *);
61
62 /*
63  *      The driver interface for read/write uses a block
64  *      of memory in the ISA I/O memory space allocated via
65  *      an ioctl setting.
66  *
67  *      Now that we have different bus attachments, we should really
68  *      use a better algorythm to allocate memory.
69  */
70 static unsigned long pccard_mem;        /* Physical memory */
71 static unsigned char *pccard_kmem;      /* Kernel virtual address */
72 static struct resource *pccard_mem_res;
73 static int pccard_mem_rid;
74
75 static  d_open_t        crdopen;
76 static  d_close_t       crdclose;
77 static  d_read_t        crdread;
78 static  d_write_t       crdwrite;
79 static  d_ioctl_t       crdioctl;
80 static  d_poll_t        crdpoll;
81
82 #define CDEV_MAJOR 50
83 static struct cdevsw crd_cdevsw = {
84         /* name */      "crd",
85         /* maj */       CDEV_MAJOR,
86         /* flags */     0,
87         /* port */      NULL,
88         /* autoq */     0,
89
90         /* open */      crdopen,
91         /* close */     crdclose,
92         /* read */      crdread,
93         /* write */     crdwrite,
94         /* ioctl */     crdioctl,
95         /* poll */      crdpoll,
96         /* mmap */      nommap,
97         /* strategy */  nostrategy,
98         /* dump */      nodump,
99         /* psize */     nopsize
100 };
101
102 /*
103  *      Power off the slot.
104  *      (doing it immediately makes the removal of some cards unstable)
105  */
106 static void
107 power_off_slot(void *arg)
108 {
109         struct slot *slt = (struct slot *)arg;
110         int s;
111
112         /*
113          * The following will generate an interrupt.  So, to hold off
114          * the interrupt unitl after disable runs so that we can get rid
115          * rid of the interrupt before it becomes unsafe to touch the
116          * device.
117          *
118          * XXX In current, the spl stuff is a nop.
119          */
120         s = splhigh();
121         /* Power off the slot. */
122         slt->pwr_off_pending = 0;
123         slt->ctrl->disable(slt);
124         splx(s);
125 }
126
127 /*
128  *      disable_slot - Disables the slot by removing
129  *      the power and unmapping the I/O
130  */
131 static void
132 disable_slot(struct slot *slt)
133 {
134         device_t pccarddev;
135         device_t *kids;
136         int nkids;
137         int i;
138         int ret;
139
140         /*
141          * Note that a race condition is possible here; if a
142          * driver is accessing the device and it is removed, then
143          * all bets are off...
144          */
145         pccarddev = slt->dev;
146         device_get_children(pccarddev, &kids, &nkids);
147         for (i = 0; i < nkids; i++) {
148                 if ((ret = device_delete_child(pccarddev, kids[i])) != 0)
149                         printf("pccard: delete of %s failed: %d\n",
150                                 device_get_nameunit(kids[i]), ret);
151         }
152         free(kids, M_TEMP);
153
154         /* Power off the slot 1/2 second after removal of the card */
155         slt->poff_ch = timeout(power_off_slot, (caddr_t)slt, hz / 2);
156         slt->pwr_off_pending = 1;
157 }
158
159 static void
160 disable_slot_to(struct slot *slt)
161 {
162         disable_slot(slt);
163         if (slt->state == empty)
164                 printf("pccard: card removed, slot %d\n", slt->slotnum);
165         else
166                 printf("pccard: card deactivated, slot %d\n", slt->slotnum);
167         pccard_remove_beep();
168         selwakeup(&slt->selp);
169 }
170
171 /*
172  *      pccard_init_slot - Initialize the slot controller and attach various
173  * things to it.  We also make the device for it.  We create the device that
174  * will be exported to devfs.
175  */
176 struct slot *
177 pccard_init_slot(device_t dev, struct slot_ctrl *ctrl)
178 {
179         int             slotno;
180         struct slot     *slt;
181
182         slt = PCCARD_DEVICE2SOFTC(dev);
183         slotno = device_get_unit(dev);
184         slt->dev = dev;
185         slt->d = make_dev(&crd_cdevsw, slotno, 0, 0, 0600, "card%d", slotno);
186         slt->d->si_drv1 = slt;
187         slt->ctrl = ctrl;
188         slt->slotnum = slotno;
189         callout_handle_init(&slt->insert_ch);
190         callout_handle_init(&slt->poff_ch);
191
192         return (slt);
193 }
194
195 /*
196  *      allocate_driver - Create a new device entry for this
197  *      slot, and attach a driver to it.
198  */
199 static int
200 allocate_driver(struct slot *slt, struct dev_desc *desc)
201 {
202         struct pccard_devinfo *devi;
203         device_t pccarddev;
204         int err, irq = 0;
205         device_t child;
206         device_t *devs;
207         int count;
208
209         pccarddev = slt->dev;
210         err = device_get_children(pccarddev, &devs, &count);
211         if (err != 0)
212                 return (err);
213         free(devs, M_TEMP);
214         if (count) {
215                 device_printf(pccarddev,
216                     "Can not attach more than one child.\n");
217                 return (EIO);
218         }
219         irq = ffs(desc->irqmask) - 1;
220         MALLOC(devi, struct pccard_devinfo *, sizeof(*devi), M_DEVBUF,
221             M_WAITOK | M_ZERO);
222         strcpy(devi->name, desc->name);
223         /*
224          *      Create an entry for the device under this slot.
225          */
226         devi->running = 1;
227         devi->slt = slt;
228         bcopy(desc->misc, devi->misc, sizeof(desc->misc));
229         strcpy(devi->manufstr, desc->manufstr);
230         strcpy(devi->versstr, desc->versstr);
231         devi->manufacturer = desc->manufacturer;
232         devi->product = desc->product;
233         devi->prodext = desc->prodext;
234         resource_list_init(&devi->resources);
235         child = device_add_child(pccarddev, devi->name, desc->unit);
236         if (child == NULL) {
237                 if (desc->unit != -1)
238                         device_printf(pccarddev,
239                             "Unit %d failed for %s, try a different unit\n",
240                             desc->unit, devi->name);
241                 else
242                         device_printf(pccarddev,
243                             "No units available for %s.  Impossible?\n",
244                             devi->name);
245                 return (EIO);
246         }
247         device_set_flags(child, desc->flags);
248         device_set_ivars(child, devi);
249         if (bootverbose) {
250                 device_printf(pccarddev, "Assigning %s:",
251                     device_get_nameunit(child));
252                 if (desc->iobase)
253                         printf(" io 0x%x-0x%x",
254                             desc->iobase, desc->iobase + desc->iosize - 1);
255                 if (irq)
256                         printf(" irq %d", irq);
257                 if (desc->mem)
258                         printf(" mem 0x%lx-0x%lx", desc->mem,
259                             desc->mem + desc->memsize - 1);
260                 printf(" flags 0x%x\n", desc->flags);
261         }
262         err = bus_set_resource(child, SYS_RES_IOPORT, 0, desc->iobase,
263             desc->iosize);
264         if (err)
265                 goto err;
266         if (irq)
267                 err = bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1);
268         if (err)
269                 goto err;
270         if (desc->memsize) {
271                 err = bus_set_resource(child, SYS_RES_MEMORY, 0, desc->mem,
272                     desc->memsize);
273                 if (err)
274                         goto err;
275         }
276         err = device_probe_and_attach(child);
277         /*
278          * XXX We unwisely assume that the detach code won't run while the
279          * XXX the attach code is attaching.  Someone should put some
280          * XXX interlock code.  This can happen if probe/attach takes a while
281          * XXX and the user ejects the card, which causes the detach
282          * XXX function to be called.
283          */
284         strncpy(desc->name, device_get_nameunit(child), sizeof(desc->name));
285         desc->name[sizeof(desc->name) - 1] = '\0';
286 err:
287         if (err)
288                 device_delete_child(pccarddev, child);
289         return (err);
290 }
291
292 /*
293  *      card insert routine - Called from a timeout to debounce
294  *      insertion events.
295  */
296 static void
297 inserted(void *arg)
298 {
299         struct slot *slt = arg;
300
301         slt->state = filled;
302         /*
303          * Disable any pending timeouts for this slot, and explicitly
304          * power it off right now.  Then, re-enable the power using
305          * the (possibly new) power settings.
306          */
307         untimeout(power_off_slot, (caddr_t)slt, slt->poff_ch);
308         power_off_slot(slt);
309
310         /*
311          *      Enable 5V to the card so that the CIS can be read.  Well,
312          * enable the most natural voltage so that the CIS can be read.
313          */
314         slt->pwr.vcc = -1;
315         slt->pwr.vpp = -1;
316         slt->ctrl->power(slt);
317
318         printf("pccard: card inserted, slot %d\n", slt->slotnum);
319         pccard_insert_beep();
320         slt->ctrl->reset(slt);
321 }
322
323 /*
324  *      Card event callback. Called at splhigh to prevent
325  *      device interrupts from interceding.
326  */
327 void
328 pccard_event(struct slot *slt, enum card_event event)
329 {
330         if (slt->insert_seq) {
331                 slt->insert_seq = 0;
332                 untimeout(inserted, (void *)slt, slt->insert_ch);
333         }
334
335         switch(event) {
336         case card_removed:
337         case card_deactivated:
338                 if (slt->state == filled || slt->state == inactive) {
339                         if (event == card_removed)
340                                 slt->state = empty;
341                         else
342                                 slt->state = inactive;
343                         disable_slot_to(slt);
344                 }
345                 break;
346         case card_inserted:
347                 slt->insert_seq = 1;
348                 slt->insert_ch = timeout(inserted, (void *)slt, hz/4);
349                 break;
350         }
351 }
352
353 /*
354  *      Device driver interface.
355  */
356 static  int
357 crdopen(dev_t dev, int oflags, int devtype, d_thread_t *td)
358 {
359         struct slot *slt = PCCARD_DEV2SOFTC(dev);
360
361         if (slt == NULL)
362                 return (ENXIO);
363         if (slt->rwmem == 0)
364                 slt->rwmem = MDF_ATTR;
365         return (0);
366 }
367
368 /*
369  *      Close doesn't de-allocate any resources, since
370  *      slots may be assigned to drivers already.
371  */
372 static  int
373 crdclose(dev_t dev, int fflag, int devtype, d_thread_t *td)
374 {
375         return (0);
376 }
377
378 /*
379  *      read interface. Map memory at lseek offset,
380  *      then transfer to user space.
381  */
382 static  int
383 crdread(dev_t dev, struct uio *uio, int ioflag)
384 {
385         struct slot *slt = PCCARD_DEV2SOFTC(dev);
386         struct mem_desc *mp, oldmap;
387         unsigned char *p;
388         unsigned int offs;
389         int error = 0, win, count;
390
391         if (slt == 0 || slt->state != filled)
392                 return (ENXIO);
393         if (pccard_mem == 0)
394                 return (ENOMEM);
395         for (win = slt->ctrl->maxmem - 1; win >= 0; win--)
396                 if ((slt->mem[win].flags & MDF_ACTIVE) == 0)
397                         break;
398         if (win < 0)
399                 return (EBUSY);
400         mp = &slt->mem[win];
401         oldmap = *mp;
402         mp->flags = slt->rwmem | MDF_ACTIVE;
403         while (uio->uio_resid && error == 0) {
404                 mp->card = uio->uio_offset;
405                 mp->size = PCCARD_MEMSIZE;
406                 mp->start = (caddr_t)(void *)(uintptr_t)pccard_mem;
407                 if ((error = slt->ctrl->mapmem(slt, win)) != 0)
408                         break;
409                 offs = (unsigned int)uio->uio_offset & (PCCARD_MEMSIZE - 1);
410                 p = pccard_kmem + offs;
411                 count = MIN(PCCARD_MEMSIZE - offs, uio->uio_resid);
412                 error = uiomove(p, count, uio);
413         }
414         /*
415          *      Restore original map.
416          */
417         *mp = oldmap;
418         slt->ctrl->mapmem(slt, win);
419
420         return (error);
421 }
422
423 /*
424  *      crdwrite - Write data to card memory.
425  *      Handles wrap around so that only one memory
426  *      window is used.
427  */
428 static  int
429 crdwrite(dev_t dev, struct uio *uio, int ioflag)
430 {
431         struct slot *slt = PCCARD_DEV2SOFTC(dev);
432         struct mem_desc *mp, oldmap;
433         unsigned char *p;
434         unsigned int offs;
435         int error = 0, win, count;
436
437         if (slt == 0 || slt->state != filled)
438                 return (ENXIO);
439         if (pccard_mem == 0)
440                 return (ENOMEM);
441         for (win = slt->ctrl->maxmem - 1; win >= 0; win--)
442                 if ((slt->mem[win].flags & MDF_ACTIVE) == 0)
443                         break;
444         if (win < 0)
445                 return (EBUSY);
446         mp = &slt->mem[win];
447         oldmap = *mp;
448         mp->flags = slt->rwmem | MDF_ACTIVE;
449         while (uio->uio_resid && error == 0) {
450                 mp->card = uio->uio_offset;
451                 mp->size = PCCARD_MEMSIZE;
452                 mp->start = (caddr_t)(void *)(uintptr_t)pccard_mem;
453                 if ((error = slt->ctrl->mapmem(slt, win)) != 0)
454                         break;
455                 offs = (unsigned int)uio->uio_offset & (PCCARD_MEMSIZE - 1);
456                 p = pccard_kmem + offs;
457                 count = MIN(PCCARD_MEMSIZE - offs, uio->uio_resid);
458                 error = uiomove(p, count, uio);
459         }
460         /*
461          *      Restore original map.
462          */
463         *mp = oldmap;
464         slt->ctrl->mapmem(slt, win);
465
466         return (error);
467 }
468
469 /*
470  *      ioctl calls - allows setting/getting of memory and I/O
471  *      descriptors, and assignment of drivers.
472  */
473 static  int
474 crdioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, d_thread_t *td)
475 {
476         u_int32_t       addr;
477         int             err;
478         struct io_desc  *ip;
479         struct mem_desc *mp;
480         device_t        pccarddev;
481         int             pwval;
482         int             s;
483         struct slot     *slt = PCCARD_DEV2SOFTC(dev);
484
485         if (slt == 0 && cmd != PIOCRWMEM)
486                 return (ENXIO);
487
488         KKASSERT(td->td_proc != NULL);
489
490         switch(cmd) {
491         default:
492                 if (slt->ctrl->ioctl)
493                         return (slt->ctrl->ioctl(slt, cmd, data));
494                 return (ENOTTY);
495         /*
496          * Get slot state.
497          */
498         case PIOCGSTATE:
499                 s = splhigh();
500                 ((struct slotstate *)data)->state = slt->state;
501                 ((struct slotstate *)data)->laststate = slt->laststate;
502                 slt->laststate = slt->state;
503                 splx(s);
504                 ((struct slotstate *)data)->maxmem = slt->ctrl->maxmem;
505                 ((struct slotstate *)data)->maxio = slt->ctrl->maxio;
506                 ((struct slotstate *)data)->irqs = 0;
507                 break;
508         /*
509          * Get memory context.
510          */
511         case PIOCGMEM:
512                 s = ((struct mem_desc *)data)->window;
513                 if (s < 0 || s >= slt->ctrl->maxmem)
514                         return (EINVAL);
515                 mp = &slt->mem[s];
516                 ((struct mem_desc *)data)->flags = mp->flags;
517                 ((struct mem_desc *)data)->start = mp->start;
518                 ((struct mem_desc *)data)->size = mp->size;
519                 ((struct mem_desc *)data)->card = mp->card;
520                 break;
521         /*
522          * Set memory context. If context already active, then unmap it.
523          * It is hard to see how the parameters can be checked.
524          * At the very least, we only allow root to set the context.
525          */
526         case PIOCSMEM:
527                 if (suser(td))
528                         return (EPERM);
529                 if (slt->state != filled)
530                         return (ENXIO);
531                 s = ((struct mem_desc *)data)->window;
532                 if (s < 0 || s >= slt->ctrl->maxmem)
533                         return (EINVAL);
534                 slt->mem[s] = *((struct mem_desc *)data);
535                 return (slt->ctrl->mapmem(slt, s));
536         /*
537          * Get I/O port context.
538          */
539         case PIOCGIO:
540                 s = ((struct io_desc *)data)->window;
541                 if (s < 0 || s >= slt->ctrl->maxio)
542                         return (EINVAL);
543                 ip = &slt->io[s];
544                 ((struct io_desc *)data)->flags = ip->flags;
545                 ((struct io_desc *)data)->start = ip->start;
546                 ((struct io_desc *)data)->size = ip->size;
547                 break;
548         /*
549          * Set I/O port context.
550          */
551         case PIOCSIO:
552                 if (suser(td))
553                         return (EPERM);
554                 if (slt->state != filled)
555                         return (ENXIO);
556                 s = ((struct io_desc *)data)->window;
557                 if (s < 0 || s >= slt->ctrl->maxio)
558                         return (EINVAL);
559                 slt->io[s] = *((struct io_desc *)data);
560                 /* XXX Don't actually map */
561                 return (0);
562                 break;
563         /*
564          * Set memory window flags for read/write interface.
565          */
566         case PIOCRWFLAG:
567                 slt->rwmem = *(int *)data;
568                 break;
569         /*
570          * Set the memory window to be used for the read/write interface.
571          */
572         case PIOCRWMEM:
573                 if (*(unsigned long *)data == 0) {
574                         *(unsigned long *)data = pccard_mem;
575                         break;
576                 }
577                 if (suser(td))
578                         return (EPERM);
579                 /*
580                  * Validate the memory by checking it against the I/O
581                  * memory range. It must also start on an aligned block size.
582                  */
583                 if (*(unsigned long *)data & (PCCARD_MEMSIZE-1))
584                         return (EINVAL);
585                 pccarddev = PCCARD_DEV2SOFTC(dev)->dev;
586                 pccard_mem_rid = 0;
587                 addr = *(unsigned long *)data;
588                 if (pccard_mem_res)
589                         bus_release_resource(pccarddev, SYS_RES_MEMORY,
590                             pccard_mem_rid, pccard_mem_res);
591                 pccard_mem_res = bus_alloc_resource(pccarddev, SYS_RES_MEMORY,
592                     &pccard_mem_rid, addr, addr, PCCARD_MEMSIZE,
593                     RF_ACTIVE | rman_make_alignment_flags(PCCARD_MEMSIZE));
594                 if (pccard_mem_res == NULL)
595                         return (EINVAL);
596                 pccard_mem = rman_get_start(pccard_mem_res);
597                 pccard_kmem = rman_get_virtual(pccard_mem_res);
598                 break;
599         /*
600          * Set power values.
601          */
602         case PIOCSPOW:
603                 slt->pwr = *(struct power *)data;
604                 return (slt->ctrl->power(slt));
605         /*
606          * Allocate a driver to this slot.
607          */
608         case PIOCSDRV:
609                 if (suser(td))
610                         return (EPERM);
611                 err = allocate_driver(slt, (struct dev_desc *)data);
612                 if (!err)
613                         pccard_success_beep();
614                 else
615                         pccard_failure_beep();
616                 return (err);
617         /*
618          * Virtual removal/insertion
619          */
620         case PIOCSVIR:
621                 pwval = *(int *)data;
622                 if (!pwval) {
623                         if (slt->state != filled)
624                                 return (EINVAL);
625                         pccard_event(slt, card_deactivated);
626                 } else {
627                         if (slt->state != empty && slt->state != inactive)
628                                 return (EINVAL);
629                         pccard_event(slt, card_inserted);
630                 }
631                 break;
632         case PIOCSBEEP:
633                 if (pccard_beep_select(*(int *)data)) {
634                         return (EINVAL);
635                 }
636                 break;
637         }
638         return (0);
639 }
640
641 /*
642  *      poll - Poll on exceptions will return true
643  *      when a change in card status occurs.
644  */
645 static  int
646 crdpoll(dev_t dev, int events, d_thread_t *td)
647 {
648         int     revents = 0;
649         int     s;
650         struct slot *slt = PCCARD_DEV2SOFTC(dev);
651
652         if (events & (POLLIN | POLLRDNORM))
653                 revents |= events & (POLLIN | POLLRDNORM);
654
655         if (events & (POLLOUT | POLLWRNORM))
656                 revents |= events & (POLLIN | POLLRDNORM);
657
658         s = splhigh();
659         /*
660          *      select for exception - card event.
661          */
662         if (events & POLLRDBAND)
663                 if (slt == 0 || slt->laststate != slt->state)
664                         revents |= POLLRDBAND;
665
666         if (revents == 0)
667                 selrecord(td, &slt->selp);
668
669         splx(s);
670         return (revents);
671 }
672
673 /*
674  *      APM hooks for suspending and resuming.
675  */
676 int
677 pccard_suspend(device_t dev)
678 {
679         struct slot *slt = PCCARD_DEVICE2SOFTC(dev);
680
681         /* This code stolen from pccard_event:card_removed */
682         if (slt->state == filled) {
683                 int s = splhigh();              /* nop on current */
684                 disable_slot(slt);
685                 slt->laststate = suspend;       /* for pccardd */
686                 slt->state = empty;
687                 splx(s);
688                 printf("pccard: card disabled, slot %d\n", slt->slotnum);
689         }
690         /*
691          * Disable any pending timeouts for this slot since we're
692          * powering it down/disabling now.
693          */
694         untimeout(power_off_slot, (caddr_t)slt, slt->poff_ch);
695         slt->ctrl->disable(slt);
696         return (0);
697 }
698
699 int
700 pccard_resume(device_t dev)
701 {
702         struct slot *slt = PCCARD_DEVICE2SOFTC(dev);
703
704         slt->ctrl->resume(slt);
705         return (0);
706 }