998 obsolete DMA driver interfaces should be removed
[unleashed.git] / usr / src / uts / sun4u / io / isadma.c
blob610391baa13a68617ec018fa94e12b5ec06c8236
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
26 * Copyright 2012 Garrett D'Amore <garrett@damore.org>. All rights reserved.
30 #include <sys/conf.h>
31 #include <sys/sunddi.h>
32 #include <sys/ddi_impldefs.h>
33 #include <sys/kmem.h>
34 #include <sys/dma_i8237A.h>
35 #include <sys/isadma.h>
36 #include <sys/nexusdebug.h>
38 /* Bitfield debugging definitions for this file */
39 #define ISADMA_MAP_DEBUG 0x1
40 #define ISADMA_REGACCESS_DEBUG 0x2
43 * The isadam nexus serves two functions. The first is to represent a
44 * a placeholder in the device tree for a shared dma controller register
45 * for the SuperIO floppy and parallel ports.
46 * The second function is to virtualize the shared dma controller register
47 * for those two drivers. Rather than creating new ddi routines to manage
48 * the shared register, we will use the ddi register mapping functions to
49 * do this. The two child devices will use ddi_regs_map_setup to map in
50 * their device registers. The isadma nexus will have an aliased entry in
51 * it's own registers property for the shared dma controller register. When
52 * the isadma detects the fact that it's children are trying to map the shared
53 * register, it will intercept this mapping and provide it's own register
54 * access routine to be used to access the register when the child devices
55 * use the ddi_{get,put} calls.
57 * Sigh, the 82C37 has a weird quirk (BUG?) where when DMA is active on the
58 * the bus, PIO's cannot happen. If they do, they generate bus faults and
59 * cause the system to panic. On PC's, the Intel processor has special
60 * req/grnt lines that prevent PIO's from occuring while DMA is in flight,
61 * unfortunately, hummingbird doesn't support this special req/grnt pair.
62 * I'm going to try and work around this by implementing a cv to stop PIO's
63 * from occuring while DMA is in flight. When each child wants to do DMA,
64 * they need to mask out all other channels using the allmask register.
65 * This nexus keys on this access and locks down the hardware using a cv.
66 * Once the driver's interrupt handler is called it needs to clear
67 * the allmask register. The nexus keys off of this an issues cv wakeups
68 * if necessary.
71 * Function prototypes for busops routines:
73 static int isadma_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
74 off_t off, off_t len, caddr_t *addrp);
77 * function prototypes for dev ops routines:
79 static int isadma_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
80 static int isadma_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
83 * general function prototypes:
87 * bus ops and dev ops structures:
89 static struct bus_ops isadma_bus_ops = {
90 BUSO_REV,
91 isadma_map,
92 NULL,
93 NULL,
94 NULL,
95 i_ddi_map_fault,
96 NULL,
97 ddi_dma_allochdl,
98 ddi_dma_freehdl,
99 ddi_dma_bindhdl,
100 ddi_dma_unbindhdl,
101 ddi_dma_flush,
102 ddi_dma_win,
103 ddi_dma_mctl,
104 ddi_ctlops,
105 ddi_bus_prop_op,
106 0, /* (*bus_get_eventcookie)(); */
107 0, /* (*bus_add_eventcall)(); */
108 0, /* (*bus_remove_eventcall)(); */
109 0, /* (*bus_post_event)(); */
110 0, /* (*bus_intr_control)(); */
111 0, /* (*bus_config)(); */
112 0, /* (*bus_unconfig)(); */
113 0, /* (*bus_fm_init)(); */
114 0, /* (*bus_fm_fini)(); */
115 0, /* (*bus_fm_access_enter)(); */
116 0, /* (*bus_fm_access_exit)(); */
117 0, /* (*bus_power)(); */
118 i_ddi_intr_ops /* (*bus_intr_op(); */
121 static struct dev_ops isadma_ops = {
122 DEVO_REV,
124 ddi_no_info,
125 nulldev,
127 isadma_attach,
128 isadma_detach,
129 nodev,
130 (struct cb_ops *)0,
131 &isadma_bus_ops,
132 NULL,
133 ddi_quiesce_not_needed, /* quiesce */
137 * module definitions:
139 #include <sys/modctl.h>
141 static struct modldrv modldrv = {
142 &mod_driverops, /* Type of module. This one is a driver */
143 "isadma nexus driver", /* Name of module. */
144 &isadma_ops, /* driver ops */
147 static struct modlinkage modlinkage = {
148 MODREV_1, (void *)&modldrv, NULL
152 * driver global data:
154 static void *per_isadma_state; /* per-isadma soft state pointer */
156 /* Global debug data */
157 uint64_t isadma_sleep_cnt = 0;
158 uint64_t isadma_wakeup_cnt = 0;
159 #ifdef DEBUG
160 int64_t isadma_max_waiter = 0;
161 int64_t isadma_min_waiter = 0xffffll;
162 uint64_t isadma_punt = 0;
163 uint64_t isadma_setting_wdip = 0;
164 uint64_t isadma_clearing_wdip = 0;
165 #endif
168 _init(void)
170 int e;
173 * Initialize per-isadma soft state pointer.
175 e = ddi_soft_state_init(&per_isadma_state,
176 sizeof (isadma_devstate_t), 1);
177 if (e != 0)
178 return (e);
181 * Install the module.
183 e = mod_install(&modlinkage);
184 if (e != 0)
185 ddi_soft_state_fini(&per_isadma_state);
186 return (e);
190 _fini(void)
192 int e;
195 * Remove the module.
197 e = mod_remove(&modlinkage);
198 if (e != 0)
199 return (e);
202 * Free the soft state info.
204 ddi_soft_state_fini(&per_isadma_state);
205 return (e);
209 _info(struct modinfo *modinfop)
211 return (mod_info(&modlinkage, modinfop));
214 /* device driver entry points */
217 * attach entry point:
219 static int
220 isadma_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
222 isadma_devstate_t *isadmap; /* per isadma state pointer */
223 int32_t instance;
224 int ret = DDI_SUCCESS;
226 #ifdef DEBUG
227 debug_print_level = 0;
228 debug_info = 1;
229 #endif
230 switch (cmd) {
231 case DDI_ATTACH: {
233 * Allocate soft state for this instance.
235 instance = ddi_get_instance(dip);
236 if (ddi_soft_state_zalloc(per_isadma_state, instance)
237 != DDI_SUCCESS) {
238 ret = DDI_FAILURE;
239 goto exit;
241 isadmap = ddi_get_soft_state(per_isadma_state, instance);
242 isadmap->isadma_dip = dip;
244 /* Cache our register property */
245 if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
246 "reg", (caddr_t)&isadmap->isadma_regp,
247 &isadmap->isadma_reglen) != DDI_SUCCESS) {
248 ret = DDI_FAILURE;
249 goto fail_get_prop;
252 /* Initialize our mutex */
253 mutex_init(&isadmap->isadma_access_lock, NULL, MUTEX_DRIVER,
254 NULL);
256 /* Initialize our condition variable */
257 cv_init(&isadmap->isadma_access_cv, NULL, CV_DRIVER, NULL);
259 ddi_report_dev(dip);
260 goto exit;
263 case DDI_RESUME:
264 default:
265 goto exit;
268 fail_get_prop:
269 ddi_soft_state_free(per_isadma_state, instance);
271 exit:
272 return (ret);
276 * detach entry point:
278 static int
279 isadma_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
281 int instance = ddi_get_instance(dip);
282 isadma_devstate_t *isadmap =
283 ddi_get_soft_state(per_isadma_state, instance);
285 switch (cmd) {
286 case DDI_DETACH:
287 cv_destroy(&isadmap->isadma_access_cv);
289 mutex_destroy(&isadmap->isadma_access_lock);
291 /* free the cached register property */
292 kmem_free(isadmap->isadma_regp, isadmap->isadma_reglen);
294 ddi_soft_state_free(per_isadma_state, instance);
295 return (DDI_SUCCESS);
297 case DDI_SUSPEND:
298 return (DDI_SUCCESS);
300 return (DDI_FAILURE);
304 #ifdef DEBUG
305 static void
306 isadma_check_waiters(isadma_devstate_t *isadmap)
308 if (isadmap->isadma_want > isadma_max_waiter)
309 isadma_max_waiter = isadmap->isadma_want;
311 if (isadmap->isadma_want < isadma_min_waiter)
312 isadma_min_waiter = isadmap->isadma_want;
314 #endif
316 static void
317 isadma_dmawait(isadma_devstate_t *isadmap)
320 ASSERT(mutex_owned(&isadmap->isadma_access_lock));
322 /* Wait loop, if the locking dip is set, we wait. */
323 while (isadmap->isadma_ldip != NULL) {
325 isadmap->isadma_want++;
326 cv_wait(&isadmap->isadma_access_cv,
327 &isadmap->isadma_access_lock);
328 isadmap->isadma_want--;
329 isadma_sleep_cnt++;
333 static void
334 isadma_wakeup(isadma_devstate_t *isadmap)
337 ASSERT(mutex_owned(&isadmap->isadma_access_lock));
340 * If somebody wants register access and the lock dip is not set
341 * signal the waiters.
343 if (isadmap->isadma_want > 0 && isadmap->isadma_ldip == NULL) {
344 cv_signal(&isadmap->isadma_access_cv);
345 isadma_wakeup_cnt++;
351 * Register access vectors
354 /*ARGSUSED*/
355 void
356 isadma_norep_get8(ddi_acc_impl_t *handle, uint8_t *host_addr,
357 uint8_t *dev_addr, size_t repcount, uint_t flags)
361 /*ARGSUSED*/
362 void
363 isadma_norep_get16(ddi_acc_impl_t *handle, uint16_t *host_addr,
364 uint16_t *dev_addr, size_t repcount, uint_t flags)
368 /*ARGSUSED*/
369 void
370 isadma_norep_get32(ddi_acc_impl_t *handle, uint32_t *host_addr,
371 uint32_t *dev_addr, size_t repcount, uint_t flags)
375 /*ARGSUSED*/
376 void
377 isadma_norep_get64(ddi_acc_impl_t *handle, uint64_t *host_addr,
378 uint64_t *dev_addr, size_t repcount, uint_t flags)
382 /*ARGSUSED*/
383 void
384 isadma_norep_put8(ddi_acc_impl_t *handle, uint8_t *host_addr,
385 uint8_t *dev_addr, size_t repcount, uint_t flags)
389 /*ARGSUSED*/
390 void
391 isadma_norep_put16(ddi_acc_impl_t *handle, uint16_t *host_addr,
392 uint16_t *dev_addr, size_t repcount, uint_t flags)
396 /*ARGSUSED*/
397 void
398 isadma_norep_put32(ddi_acc_impl_t *handle, uint32_t *host_addr,
399 uint32_t *dev_addr, size_t repcount, uint_t flags)
403 /*ARGSUSED*/
404 void
405 isadma_norep_put64(ddi_acc_impl_t *handle, uint64_t *host_addr,
406 uint64_t *dev_addr, size_t repcount, uint_t flags)
410 /*ARGSUSED*/
411 uint8_t
412 isadma_get8(ddi_acc_impl_t *hdlp, uint8_t *addr)
414 ddi_acc_handle_t phdl = hdlp->ahi_common.ah_platform_private;
415 isadma_devstate_t *isadmap = hdlp->ahi_common.ah_bus_private;
416 off_t offset = (caddr_t)addr - hdlp->ahi_common.ah_addr;
417 uint8_t ret = 0xff;
419 if (IN_CHILD_SPACE(offset)) { /* Pass to parent */
420 #ifdef DEBUG
421 isadma_punt++;
422 #endif
423 return (ddi_get8(phdl, addr));
425 #ifdef DEBUG
426 isadma_check_waiters(isadmap);
427 #endif
428 mutex_enter(&isadmap->isadma_access_lock);
429 isadma_dmawait(isadmap); /* wait until on-going dma completes */
431 /* No 8 bit access to 16 bit address or count registers */
432 if (IN_16BIT_SPACE(offset))
433 goto exit;
435 /* No 8 bit access to first/last flip-flop registers */
436 if (IS_SEQREG(offset))
437 goto exit;
439 ret = ddi_get8(phdl, addr); /* Pass to parent */
440 exit:
441 isadma_wakeup(isadmap);
442 mutex_exit(&isadmap->isadma_access_lock);
443 return (ret);
447 * Allow child devices to access this shared register set as if it were
448 * a real 16 bit register. The ISA bridge defines the access to this
449 * 16 bit dma controller & count register by programming an 8 byte register.
451 /*ARGSUSED*/
452 uint16_t
453 isadma_get16(ddi_acc_impl_t *hdlp, uint16_t *addr)
455 ddi_acc_handle_t phdl = hdlp->ahi_common.ah_platform_private;
456 isadma_devstate_t *isadmap = hdlp->ahi_common.ah_bus_private;
457 off_t offset = (caddr_t)addr - hdlp->ahi_common.ah_addr;
458 uint16_t ret = 0xffff;
460 if (IN_CHILD_SPACE(offset)) { /* Pass to parent */
461 #ifdef DEBUG
462 isadma_punt++;
463 #endif
464 return (ddi_get16(phdl, addr));
466 #ifdef DEBUG
467 isadma_check_waiters(isadmap);
468 #endif
469 mutex_enter(&isadmap->isadma_access_lock);
470 isadma_dmawait(isadmap); /* wait until on-going dma completes */
472 /* Only Allow access to the 16 bit count and address registers */
473 if (!IN_16BIT_SPACE(offset))
474 goto exit;
476 /* Set the sequencing register to the low byte */
477 ddi_put8(phdl, (uint8_t *)HDL_TO_SEQREG_ADDR(hdlp, offset), 0);
479 /* Read the low byte, then high byte */
480 ret = ddi_get8(phdl, (uint8_t *)addr);
481 ret = (ddi_get8(phdl, (uint8_t *)addr) << 8) | ret;
482 exit:
483 isadma_wakeup(isadmap);
484 mutex_exit(&isadmap->isadma_access_lock);
485 return (ret);
488 /*ARGSUSED*/
489 uint32_t
490 isadma_noget32(ddi_acc_impl_t *hdlp, uint32_t *addr)
492 return (UINT32_MAX);
495 /*ARGSUSED*/
496 uint64_t
497 isadma_noget64(ddi_acc_impl_t *hdlp, uint64_t *addr)
499 return (UINT64_MAX);
503 * Here's where we do our locking magic. The dma all mask register is an 8
504 * bit register in the dma space, so we look for the access to the
505 * DMAC1_ALLMASK register. When somebody is masking out the dma channels
506 * we lock down the dma engine from further PIO accesses. When the driver
507 * calls back into this routine to clear the allmask register, we wakeup
508 * any blocked threads.
510 /*ARGSUSED*/
511 void
512 isadma_put8(ddi_acc_impl_t *hdlp, uint8_t *addr, uint8_t value)
514 ddi_acc_handle_t phdl = hdlp->ahi_common.ah_platform_private;
515 isadma_devstate_t *isadmap = hdlp->ahi_common.ah_bus_private;
516 off_t offset = (caddr_t)addr - hdlp->ahi_common.ah_addr;
518 if (IN_CHILD_SPACE(offset)) { /* Pass to parent */
519 #ifdef DEBUG
520 isadma_punt++;
521 #endif
522 ddi_put8(phdl, addr, value);
523 return;
525 #ifdef DEBUG
526 isadma_check_waiters(isadmap);
527 #endif
528 mutex_enter(&isadmap->isadma_access_lock);
530 if (isadmap->isadma_ldip == hdlp->ahi_common.ah_dip) { /* owned lock? */
531 if (END_ISADMA(offset, value)) {
532 isadmap->isadma_ldip = NULL; /* reset lock owner */
533 #ifdef DEBUG
534 isadma_clearing_wdip++;
535 #endif
537 } else { /* we don't own the lock */
538 /* wait until on-going dma completes */
539 isadma_dmawait(isadmap);
541 if (BEGIN_ISADMA(offset, value)) {
542 isadmap->isadma_ldip = hdlp->ahi_common.ah_dip;
543 #ifdef DEBUG
544 isadma_setting_wdip++;
545 #endif
549 /* No 8 bit access to 16 bit address or count registers */
550 if (IN_16BIT_SPACE(offset))
551 goto exit;
553 /* No 8 bit access to first/last flip-flop registers */
554 if (IS_SEQREG(offset))
555 goto exit;
557 ddi_put8(phdl, addr, value); /* Pass to parent */
558 exit:
559 isadma_wakeup(isadmap);
560 mutex_exit(&isadmap->isadma_access_lock);
564 * Allow child devices to access this shared register set as if it were
565 * a real 16 bit register. The ISA bridge defines the access to this
566 * 16 bit dma controller & count register by programming an 8 byte register.
568 /*ARGSUSED*/
569 void
570 isadma_put16(ddi_acc_impl_t *hdlp, uint16_t *addr, uint16_t value)
572 ddi_acc_handle_t phdl = hdlp->ahi_common.ah_platform_private;
573 isadma_devstate_t *isadmap = hdlp->ahi_common.ah_bus_private;
574 off_t offset = (caddr_t)addr - hdlp->ahi_common.ah_addr;
576 if (IN_CHILD_SPACE(offset)) { /* Pass to parent */
577 #ifdef DEBUG
578 isadma_punt++;
579 #endif
580 ddi_put16(phdl, addr, value);
581 return;
583 #ifdef DEBUG
584 isadma_check_waiters(isadmap);
585 #endif
586 mutex_enter(&isadmap->isadma_access_lock);
587 isadma_dmawait(isadmap); /* wait until on-going dma completes */
589 /* Only Allow access to the 16 bit count and address registers */
590 if (!IN_16BIT_SPACE(offset))
591 goto exit;
593 /* Set the sequencing register to the low byte */
594 ddi_put8(phdl, (uint8_t *)HDL_TO_SEQREG_ADDR(hdlp, offset), 0);
596 /* Write the low byte, then the high byte */
597 ddi_put8(phdl, (uint8_t *)addr, value & 0xff);
598 ddi_put8(phdl, (uint8_t *)addr, (value >> 8) & 0xff);
599 exit:
600 isadma_wakeup(isadmap);
601 mutex_exit(&isadmap->isadma_access_lock);
604 /*ARGSUSED*/
605 void
606 isadma_noput32(ddi_acc_impl_t *hdlp, uint32_t *addr, uint32_t value) {}
608 /*ARGSUSED*/
609 void
610 isadma_noput64(ddi_acc_impl_t *hdlp, uint64_t *addr, uint64_t value) {}
612 #define IS_SAME_REG(r1, r2) (((r1)->ebus_addr_hi == (r2)->ebus_addr_hi) && \
613 ((r1)->ebus_addr_low == (r2)->ebus_addr_low))
616 * The isadma_map routine determines if it's child is attempting to map a
617 * shared reg. If it is, it installs it's own vectors and bus private pointer
618 * and stacks those ops that were already defined.
620 static int
621 isadma_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
622 off_t off, off_t len, caddr_t *addrp)
624 isadma_devstate_t *isadmap = ddi_get_soft_state(per_isadma_state,
625 ddi_get_instance(dip));
626 dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
627 ebus_regspec_t *child_regp, *regp;
628 int32_t rnumber = mp->map_obj.rnumber;
629 int32_t reglen;
630 int ret;
631 ddi_acc_impl_t *hp;
634 * Get child regspec since the mapping struct may not have it yet
636 if (ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS,
637 "reg", (caddr_t)&regp, &reglen) != DDI_SUCCESS) {
638 return (DDI_FAILURE);
641 child_regp = regp + rnumber;
643 DPRINTF(ISADMA_MAP_DEBUG, ("isadma_map: child regp %p "
644 "parent regp %p Child reg array %p\n", (void *)child_regp,
645 (void *)isadmap->isadma_regp, (void *)regp));
647 /* Figure out if we're mapping or unmapping */
648 switch (mp->map_op) {
649 case DDI_MO_MAP_LOCKED:
650 /* Call up device tree to establish mapping */
651 ret = (DEVI(pdip)->devi_ops->devo_bus_ops->bus_map)
652 (pdip, rdip, mp, off, len, addrp);
654 if ((ret != DDI_SUCCESS) ||
655 !IS_SAME_REG(child_regp, isadmap->isadma_regp))
656 break;
658 /* Post-process the mapping request. */
659 hp = kmem_alloc(sizeof (ddi_acc_impl_t), KM_SLEEP);
660 *hp = *(ddi_acc_impl_t *)mp->map_handlep;
661 impl_acc_hdl_get((ddi_acc_handle_t)mp->map_handlep)->
662 ah_platform_private = hp;
663 hp = (ddi_acc_impl_t *)mp->map_handlep;
664 hp->ahi_common.ah_bus_private = isadmap;
665 hp->ahi_get8 = isadma_get8;
666 hp->ahi_get16 = isadma_get16;
667 hp->ahi_get32 = isadma_noget32;
668 hp->ahi_get64 = isadma_noget64;
669 hp->ahi_put8 = isadma_put8;
670 hp->ahi_put16 = isadma_put16;
671 hp->ahi_put32 = isadma_noput32;
672 hp->ahi_put64 = isadma_noput64;
673 hp->ahi_rep_get8 = isadma_norep_get8;
674 hp->ahi_rep_get16 = isadma_norep_get16;
675 hp->ahi_rep_get32 = isadma_norep_get32;
676 hp->ahi_rep_get64 = isadma_norep_get64;
677 hp->ahi_rep_put8 = isadma_norep_put8;
678 hp->ahi_rep_put16 = isadma_norep_put16;
679 hp->ahi_rep_put32 = isadma_norep_put32;
680 hp->ahi_rep_put64 = isadma_norep_put64;
681 break;
683 case DDI_MO_UNMAP:
684 if (IS_SAME_REG(child_regp, isadmap->isadma_regp)) {
685 hp = impl_acc_hdl_get(
686 (ddi_acc_handle_t)mp->map_handlep)->
687 ah_platform_private;
688 *(ddi_acc_impl_t *)mp->map_handlep = *hp;
689 kmem_free(hp, sizeof (ddi_acc_impl_t));
692 /* Call up tree to tear down mapping */
693 ret = (DEVI(pdip)->devi_ops->devo_bus_ops->bus_map)
694 (pdip, rdip, mp, off, len, addrp);
695 break;
697 default:
698 ret = DDI_FAILURE;
699 break;
702 kmem_free(regp, reglen);
703 return (ret);