/* $Header: /korea/src/ccd_utilities/device_driver/Solaris/Rev1/RCS/astro.c,v 1.8 1998/05/08 18:06:14 lopez Exp $ */ /* * astro.c -- Solaris device driver for the SDSU SBus Interface Card. * * VERSION 1.6 winter 1997 - 1998 * */ /* * Development notes: * No standard include files outside of the directory can be * included since this code runs in kernel space. Only functions and * structures in Section 9 of the manual are usable here. (Yes, that * means that , , and friends and there functions * cannot be used). * * The header files and must be at the end * of the include file list. These headers undefine several macros * that are re-implemented as functions. */ #include #include #include #include #include #include #include #include #include #include #include "astro_impl.h" #include "astro_io.h" #include #include #include #include #include #include /* these two must be last */ #include /* * O.S. specfic #define's */ #ifdef SOLARIS2_6 #define ulong_t uint32_t #endif /* SOLARIS2_6 */ /* Prototypes for main entry points pointed to in the cb_ops structure */ static int astro_open (dev_t *devp, int flag, int otyp, cred_t *credp); static int astro_close (dev_t dev, int flag, int otyp, cred_t *credp); static int astro_read (dev_t dev, struct uio *uiop, cred_t *credp); static int astro_write (dev_t dev, struct uio *uiop, cred_t *credp); static int astro_ioctl (dev_t dev, int cmd, int arg, int mode, cred_t *credp, int *rvalp); static struct cb_ops astro_cb_ops = { astro_open, astro_close, nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ astro_read, astro_write, astro_ioctl, nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* chpoll */ ddi_prop_op, /* prop_op */ NULL, (D_NEW | D_MP) }; /* Prototypes for functions pointed to by the dev_ops structure. */ static int astro_getinfo (dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); static int astro_identify (dev_info_t *dip); static int astro_attach (dev_info_t *dip, ddi_attach_cmd_t cmd); static int astro_detach (dev_info_t *dip, ddi_detach_cmd_t cmd); /* * Notes: * 1> the reset(9E) entry point is not supported by the present * system (SunOS 5.4); * 2> the devo_bus_ops field must be NULL since this is a leaf node * driver. */ static struct dev_ops astro_ops = { DEVO_REV, 0, astro_getinfo, astro_identify, nulldev, astro_attach, astro_detach, nodev, /* reset */ &astro_cb_ops, NULL /* bus_ops */ }; /* * The modldrv structure is similar to the old vdldrv structure * but with some field changes. */ extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, "astro driver v1.6", &astro_ops }; /* The modlinkage structure is new in Solaris 2.x and is a necessary part of the autoconfiguration. */ static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; /* Anchor point for soft state information */ static void *statep; /* opaque */ /* * Loadable Module Support * * The 3 entry points, _init(9e), _info(9e), and _fini(9e) must be * implemented and must have these names. They are called by the * kernel during module loading and are not available to user code. */ /* * _init(9e) initializes a loadable module. It is called before any * other routine and returns the evaluation of mod_install(9f). Any * other work should be done prior to calling mod_install(9f) and must * be undone if mod_install(9f) fails. */ int _init(void) { int err = ddi_soft_state_init (&statep, sizeof (struct astro_state), 1); if (err) return err; err = mod_install (&modlinkage); if (err) ddi_soft_state_fini (&statep); return err; } int _info (struct modinfo *modinfop) { return mod_info (&modlinkage, modinfop); } /* _fini(9e) prepares the module for unloading. It should deallocate any resources allocated by _init(9e). */ int _fini (void) { int err = mod_remove (&modlinkage); if (!err) ddi_soft_state_fini (&statep); return err; } /* Prototypes for other functions */ static u_int astro_intr (caddr_t instance); static int astro_strategy (struct buf *bp); static void astro_minphys (struct buf *bp); static void astro_dma_stop (struct astro_state *asp); static void astro_dma_chain (struct astro_state *asp); static void astro_dma_reset (struct astro_state *asp); /* Driver Configuration. The driver must provide the five entry points defined in this section. Notes: o There is an implicit mapping here between the instance number and the minor number such that instance == minor number. */ static int astro_getinfo (dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { struct astro_state *xsp; dev_t dev = (dev_t) arg; switch (infocmd) { case DDI_INFO_DEVT2INSTANCE: { *result = (void *) getminor (dev); return DDI_SUCCESS; } case DDI_INFO_DEVT2DEVINFO: { int instance = getminor (dev); struct astro_state *xsp = ddi_get_soft_state (statep, instance); if (xsp == NULL) return DDI_FAILURE; *result = (void *) xsp->dip; return DDI_SUCCESS; } default: return DDI_FAILURE; } } static int astro_identify (dev_info_t *dip) { if (strcmp (ddi_get_name (dip), prom_id_string_one) == 0) return DDI_IDENTIFIED; else if (strcmp (ddi_get_name (dip), prom_id_string_two) == 0) return DDI_IDENTIFIED; else if (strcmp (ddi_get_name (dip), prom_id_string_three) == 0) return DDI_IDENTIFIED; else { cmn_err (CE_WARN, "astro_identify: DDI_NOT_IDENTIFIED"); return DDI_NOT_IDENTIFIED; } } /* * For a character device (memory-mapped), attach(9e) needs to: * * 1. allocate a state structure and initialize it; * 2. map the device's registers; * 3. add the interrupt handler; * 4. initialize mutexes and condition variables; * 5. create the device's minor node. */ static int astro_attach (dev_info_t *dip, ddi_attach_cmd_t cmd) { struct astro_state *asp = NULL; struct image_buf *ip; int instance = ddi_get_instance (dip); volatile u_long status; int j; enum { false, true } success = true; /* optimist! */ switch (cmd) { case DDI_ATTACH: { /* quit if device is slave-only -- can't do DMA then */ if (ddi_slaveonly (dip) == DDI_SUCCESS) { success = false; break; } /* quit if interrupt level is too high */ if (ddi_intr_hilevel (dip, 0) != 0) { cmn_err (CE_WARN, "astro: high-level interrupts are not supported"); success = false; break; } /* allocate the soft state structure */ if (ddi_soft_state_zalloc (statep, instance) != 0) { success = false; break; } asp = ddi_get_soft_state (statep, instance); asp->dip = dip; asp->busy = 0; ddi_set_driver_private(dip, (caddr_t)asp); /* Allocate kernel memory for three buffers */ for (j = 0; j < 3; j++) { ip = &asp->image_buf[j]; } /* map the device registers */ if ( ddi_regs_map_setup ( dip, 0, (caddr_t *)&asp->regp, 0, sizeof(struct astro_regs), &access_attr, &asp->reg_access_handle) != DDI_SUCCESS) { success = false; break; } if (ddi_map_regs (dip, 1, (caddr_t *)&asp->dchan, 0, sizeof (struct Dchan_regs)) != DDI_SUCCESS) { success = false; break; } if (ddi_map_regs (dip, 2, (caddr_t *)&asp->echan, 0, sizeof (struct Echan_regs)) != DDI_SUCCESS) { success = false; break; } /* register interrupt handler and initialize the locks */ /* first register a null handler */ if (ddi_add_intr (dip, 0, &asp->iblock_cookie, NULL, (u_int (*)(caddr_t))nulldev, NULL) != DDI_SUCCESS) { success = false; break; } /* then initialize locks */ mutex_init (&asp->mu, "astro mutex", MUTEX_DRIVER, (void *)&asp->iblock_cookie); cv_init (&asp->cv, "astro cv", CV_DRIVER, NULL); cv_init (&asp->bufcv, "astro bufcv", CV_DRIVER, NULL); /* remove the null handler */ ddi_remove_intr (dip, 0, asp->iblock_cookie); /* and register the real interrupt handler */ if (ddi_add_intr (dip, 0, &asp->iblock_cookie, (ddi_idevice_cookie_t *)NULL, astro_intr, (caddr_t)instance) != DDI_SUCCESS) { success = false; break; } /* create minor node */ if (ddi_create_minor_node (dip, "astro0", S_IFCHR, instance, "ddi_sdsu_astro", 0) != DDI_SUCCESS) { success = false; break; } /* create another minor node (for a dual read-out board) */ if (ddi_create_minor_node (dip, "astro1", S_IFCHR, instance+1, "ddi_sdsu_astro", 0) != DDI_SUCCESS) { success = false; break; } /* complete initializing soft state structure */ asp->dip = dip; asp->open = 0; /* put device in quiescent state */ status = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr); while (status & dmac_draining) status = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr); ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)(status & ~dmac_en_dma) ); status = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr); ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)dmac_reset ); ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)0x0 ); /* these are not really dmac registers so clear them too */ ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->unused, (ulong_t)0x0 ); ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->ww, (ulong_t)0x0 ); ddi_report_dev (dip); } break; default: { return DDI_FAILURE; } } if (success == false) { /* deallocate any resources allocated above */ ddi_remove_minor_node (dip, NULL); /* removes all minor nodes */ ddi_remove_intr (dip, 0, asp->iblock_cookie); if (asp->regp != NULL) ddi_unmap_regs (dip, 0, (caddr_t *)&asp->regp, 0, sizeof (struct astro_regs)); if (asp->dchan != NULL) ddi_unmap_regs (dip, 1, (caddr_t *)&asp->dchan, 0, sizeof (struct Dchan_regs)); if (asp->echan != NULL) ddi_unmap_regs (dip, 2, (caddr_t *)&asp->echan, 0, sizeof (struct Echan_regs)); cv_destroy (&asp->cv); mutex_destroy (&asp->mu); if (asp != NULL) ddi_soft_state_free (asp, instance); return DDI_FAILURE; } else return DDI_SUCCESS; } /* Essentially, detach(9e) should undo all the resource allocations that attach(9e) successfully completes. */ static int astro_detach (dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance = ddi_get_instance (dip); struct astro_state *asp = ddi_get_soft_state (statep, instance); int j; switch (cmd) { case DDI_DETACH: { /* pacify the device */ ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)dmac_reset ); for (j = 0; j < 3; j++) { } ddi_remove_minor_node (dip, NULL); /* removes all minor nodes */ ddi_remove_intr (dip, 0, asp->iblock_cookie); ddi_regs_map_free(&asp->reg_access_handle); ddi_unmap_regs (dip, 1, (caddr_t *)&asp->dchan, 0, sizeof (struct Dchan_regs)); ddi_unmap_regs (dip, 2, (caddr_t *)&asp->echan, 0, sizeof (struct Echan_regs)); cv_destroy (&asp->cv); mutex_destroy (&asp->mu); ddi_soft_state_free (statep, instance); } break; default: return DDI_FAILURE; } return DDI_SUCCESS; } /* Main entry points */ static int astro_open (dev_t *devp, int flag, int otyp, cred_t *credp) { int instance = getminor (*devp); struct astro_state *asp = ddi_get_soft_state (statep, instance); if (asp == NULL) return ENXIO; if (otyp != OTYP_CHR) return EINVAL; mutex_enter (&asp->mu); if (flag == FEXCL && asp->open > 0) /* honour exclusive lock flag */ return EAGAIN; asp->open++; astro_dma_reset(asp); ddi_putl(asp->reg_access_handle,(ulong_t *)&asp->regp->ww,(ulong_t)0x0); mutex_exit (&asp->mu); return 0; } /* end astro_open */ static int astro_close (dev_t dev, int flag, int otyp, cred_t *credp) { int instance = getminor (dev); struct astro_state *asp = ddi_get_soft_state (statep, instance); if (asp == NULL) /* sanity checks */ return ENXIO; if (otyp != OTYP_CHR) return EINVAL; mutex_enter (&asp->mu); asp->open = 0; astro_dma_reset(asp); ddi_putl(asp->reg_access_handle,(ulong_t *)&asp->regp->ww,(ulong_t)0x0); mutex_exit (&asp->mu); return 0; } /* end astro_close */ /* * Read uses the DMA controller next transfer feature, so that * during a transfer, the next byte and next address registers * are used for >64K transfers. * Now, how does all of this work? First we allocate three buffers * in kernel space for the image transfer. The address of the first * buffer is loaded onto the Address Register of the DMA and the address * of the second buffer is loaded onto the Next Address Register of the * DMA. When the first buffer is finished, the DMA will generate an * interrupt and then move the address in the Next Address Register into * it's Address Register and continue with that buffer. Between this * function, the function astro_inter (the interrupt handler) and the * function astro_dma_chain, the address of the third buffer is loaded * onto the Next Address Register of the DMA and the image in the first * buffer is transferred into user memory. The switching between all * three buffers (one filling, one emptying, one ready) is how the image * is transfered. * Here's something to think about regarding a future upgrade. Three * buffers are allocated regardless if an image to be transfered or a * reply from the boards is to be sent back. The replys don't need * all three buffers . . . */ static int astro_read (dev_t dev, struct uio *uiop, cred_t *credp) { struct image_buf *ip; int value = 0, j, sleeping; u_int bytesize, byteremain; clock_t time1, time2; u_long bcr, csr; uint_t cookies; int instance = getminor (dev); struct astro_state *asp = ddi_get_soft_state (statep, instance); if (asp == NULL) return ENXIO; mutex_enter (&asp->mu); while (asp->busy) /* allow only 1 thread at a time */ { cv_wait (&asp->cv, &asp->mu); /* until interrupt serviced */ } asp->busy = 1; mutex_exit (&asp->mu); /* Setup image buffers */ for (j = 0; j < 3; j++) { asp->image_buf[j].buf_handle = (caddr_t) 0; } for (j = 0; j < 3; j++) { ip = &asp->image_buf[j]; ip->operation = DDI_DMA_READ; ip->state = image_buf_waiting; if ( ddi_dma_alloc_handle(asp->dip, &dma_attrs, DDI_DMA_DONTWAIT, NULL, &ip->buf_handle) != DDI_SUCCESS) { cmn_err (CE_WARN,"astro_read: ddi_dma_alloc_handle failed"); value = ENOMEM; goto EXITREAD; } if ( ddi_dma_mem_alloc ( ip->buf_handle, image_buf_size, &access_attr, DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &ip->address, &ip->length, &ip->data_access_handle) != DDI_SUCCESS) { cmn_err (CE_WARN,"astro_read: ddi_dma_mem_alloc failed"); value = ENOMEM; goto EXITREAD; } if ( ddi_dma_addr_bind_handle ( ip->buf_handle, NULL, ip->address, ip->length, DDI_DMA_READ, DDI_DMA_DONTWAIT, NULL, &ip->buf_cookie, &cookies) != DDI_DMA_MAPPED) { cmn_err (CE_WARN,"astro_read: ddi_dma_addr_bind_handle failed"); value = ENOMEM; goto EXITREAD; } } asp->buf_numbusy = 0; asp->buf_numwaiting = 3; asp->buf_done = asp->buf_ready = asp->buf_busy = asp->buf_waiting = 0; bytesize = byteremain = uiop->uio_resid; /* * asp->bytremain is used by the interupt handler to see if the data * transfer has completed */ asp->byteremain = byteremain; while ((byteremain > 0) || (asp->buf_numwaiting < 3)) { if ((byteremain > 0) && (asp->buf_numwaiting > 0)) { ip = &asp->image_buf[asp->buf_waiting]; if (ip->state != image_buf_waiting) { value = ECHILD; mutex_enter (&asp->mu); astro_dma_stop(asp); mutex_exit (&asp->mu); goto EXITREAD; } if (byteremain > ip->length) ip->size = (((byteremain-1)< (ip->length)) ? (byteremain-1):(ip->length)); else ip->size = byteremain; byteremain -= ip->size; asp->byteremain = byteremain; ip->state = image_buf_ready; asp->buf_numwaiting--; asp->buf_waiting++; if (asp->buf_waiting == 3) asp->buf_waiting = 0; mutex_enter (&asp->mu); astro_dma_chain(asp); mutex_exit (&asp->mu); } else if (asp->buf_numwaiting < 3) { mutex_enter (&asp->mu); ip = &asp->image_buf[asp->buf_done]; while ((ip->state != image_buf_done) && (ip->state != image_buf_error)) { drv_getparm(LBOLT, (u_long *)&time1); time2 = time1 + drv_usectohz(dma_read_timeout_usec); sleeping = cv_timedwait_sig(&asp->bufcv,&asp->mu,time2); if (sleeping <= 0) { csr = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr); bcr = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->bcr); astro_dma_stop(asp); value = ETIME; if (sleeping == -1) cmn_err (CE_WARN,"astro_read: dma timeout csr = 0x%x", csr); ip->size = ip->size - bcr; if (ddi_dma_sync(ip->buf_handle, 0, ip->size, DDI_DMA_SYNC_FORKERNEL) != DDI_SUCCESS) cmn_err (CE_WARN,"astro_read: Unable to sync buffer"); else if (uiomove(ip->address, ip->size, UIO_READ, uiop) != 0) cmn_err (CE_WARN,"astro_read: copy error to user space"); mutex_exit(&asp->mu); goto EXITREAD; } } if (ip->state == image_buf_error) { switch(ip->det_state) { case DEVICE_NOT_BUSY: value = ENOTBLK; break; case IMAGE_BUF_NOT_BUSY: value = EDEADLK; break; case DATA_TRANS_ERROR: value = ENOLCK; break; case BAD_INTERRUPT: value = ENOTEMPTY; break; case TIMED_OUT: value = ETIME; } astro_dma_stop(asp); mutex_exit(&asp->mu); goto EXITREAD; } if (ip->state != image_buf_done) { value = EPERM; astro_dma_stop(asp); mutex_exit(&asp->mu); goto EXITREAD; } mutex_exit(&asp->mu); if (ddi_dma_sync(ip->buf_handle, 0, ip->size, DDI_DMA_SYNC_FORKERNEL) != DDI_SUCCESS) { cmn_err (CE_WARN,"astro_read: Unable to sync buffer"); value = ESPIPE; mutex_enter(&asp->mu); astro_dma_stop(asp); mutex_exit(&asp->mu); goto EXITREAD; } value = uiomove(ip->address, ip->size, UIO_READ, uiop); if (value != 0) { cmn_err (CE_WARN,"astro_read: copy error to user space"); value = EPIPE; mutex_enter(&asp->mu); astro_dma_stop(asp); mutex_exit(&asp->mu); goto EXITREAD; } ip->state = image_buf_waiting; asp->buf_numwaiting++; asp->buf_done++; if (asp->buf_done == 3) asp->buf_done = 0; } } EXITREAD: mutex_enter (&asp->mu); for (j = 0; j < 3; j++) { if (asp->image_buf[j].buf_handle != (caddr_t) -1) { ip = &asp->image_buf[j]; if ( ddi_dma_unbind_handle(ip->buf_handle) != DDI_SUCCESS ) { cmn_err(CE_WARN,"ddi_dma_unbind_handle failed"); } else { ddi_dma_mem_free(&ip->data_access_handle); ddi_dma_free_handle(&ip->buf_handle); } } } asp->busy = 0; mutex_exit(&asp->mu); cv_signal(&asp->cv); return value; } /* end astro_read */ /* astro_write is not used at this time. */ static int astro_write (dev_t dev, struct uio *uiop, cred_t *credp) { int instance = getminor (dev); struct astro_state *asp = ddi_get_soft_state (statep, instance); if (asp == NULL) return ENXIO; return 0; } /* Solaris 2.x Notes: ddi_copyin(9f) and ddi_copyout(9f) move data from/to user space to/from kernel space respectively. These must be used for this purpose. */ #ifdef __GNUC__ inline #endif static u_long extract_control_command (int cmd) { return (cmd & 0x80000000) ? ((cmd & 0x00ff) | 0x0100) : (cmd & 0x00ff); } #ifdef __GNUC__ inline #endif static u_long extract_byte_count (int cmd) { return (cmd >> 16) & 0xff; } /* Notes: o Locking should be used on a case by case basis, since it is not always necessary. Calls to allocation routines that might sleep should never be locked. */ static int astro_ioctl (dev_t dev, int cmd, int arg, int mode, cred_t *credp, int *rvalp) { int instance = getminor (dev); struct astro_state *asp = ddi_get_soft_state (statep, instance); int retval = 0; u_int iocmd; if (asp == NULL) return ENXIO; /* sanity check */ mutex_enter (&asp->mu); /* extract control command from cmd */ iocmd = extract_control_command (cmd); switch (iocmd) { case ASTRO_GET_CSR: { u_long csr =ddi_getl(asp->reg_access_handle,(ulong_t *)&asp->regp->csr); if (ddi_copyout ((caddr_t) &csr, (caddr_t) arg, sizeof (csr), mode) != 0) retval = EFAULT; } break; case ASTRO_GET_AR: { u_long ar = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->ar); if (ddi_copyout ((caddr_t) &ar, (caddr_t) arg, sizeof (ar), mode) != 0) retval = EFAULT; } break; case ASTRO_GET_BCR: { u_long bcr =ddi_getl(asp->reg_access_handle,(ulong_t *)&asp->regp->bcr); if (ddi_copyout ((caddr_t) &bcr, (caddr_t) arg, sizeof (bcr), mode) != 0) retval = EFAULT; } break; case ASTRO_SET_CSR: { long csr; if (ddi_copyin ((caddr_t) arg, (caddr_t) &csr, sizeof (csr), mode) != 0) retval = EFAULT; else { ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)csr); } } break; case ASTRO_SET_AR: { long ar; if (ddi_copyin ((caddr_t) arg, (caddr_t) &ar, sizeof (ar), mode) != 0) retval = EFAULT; else { ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->ar, (ulong_t)ar); /* make sure register write gets flushed to device */ } } break; case ASTRO_SET_BCR: { long bcr; if (ddi_copyin ((caddr_t) arg, (caddr_t) &bcr, sizeof (bcr), mode) != 0) retval = EFAULT; else { ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->bcr, (ulong_t)bcr); } } break; case ASTRO_WRITE_UNUSED_REG: { u_long bytes = extract_byte_count (cmd); u_long *ur = kmem_alloc (bytes, KM_SLEEP); u_int i; astro_dma_reset(asp); if (ddi_copyin ((caddr_t) arg, (caddr_t) ur, bytes, mode) != 0) retval = EFAULT; else { for (i=0; i<(bytes >> 2); i++) { ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->unused, (ulong_t)ur[i]); drv_usecwait (75); } } kmem_free (ur, bytes); } break; case ASTRO_SET_WW: { u_long ww; if (ddi_copyin ((caddr_t) arg, (caddr_t) &ww, sizeof (ww), mode) != 0) retval = EFAULT; else { ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->ww, (ulong_t)ww); } } break; case ASTRO_WRITE_DCHAN: { u_long bytes = extract_byte_count (cmd); u_long *dchan = kmem_alloc (bytes, KM_SLEEP); u_int i; if (ddi_copyin ((caddr_t) arg, (caddr_t) dchan, bytes, mode) != 0) retval = EFAULT; else { for (i=0; i<(bytes >> 2); i++) { readback = *(asp->dchan->dr); *(asp->dchan->dr) = dchan[i]; } } kmem_free (dchan, bytes); } break; case ASTRO_WRITE_ECHAN: { u_long bytes = extract_byte_count (cmd); u_short *echan = kmem_alloc (bytes, KM_SLEEP); u_int i; if (ddi_copyin ((caddr_t) arg, (caddr_t) echan, bytes, mode) != 0) retval = EFAULT; else { for (i=0; i<(bytes >> 2); i++) { readback = *(asp->echan->er); *(asp->echan->er + 6) = echan[i]; } } kmem_free (echan, bytes); } break; /* * The timeout features are currently not used at this time, although * it is set up so future versions may use the timeout and that versions * of ccdtool/ircam that send a timeout will not produce an error. */ case ASTRO_SET_TIMEOUT: { int temp; if (ddi_copyin ((caddr_t) arg, (caddr_t) &temp, sizeof(int), mode) != 0) retval = EFAULT; else asp->timeout = temp; } break; case ASTRO_RESET: astro_dma_reset; break; default: retval = ENOTTY; break; } mutex_exit (&asp->mu); return retval; } /* Other functions */ /* Reset DMA controller */ static void astro_dma_reset(struct astro_state *asp) { ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)(dmac_reset | dmac_write) ); ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)(0) ); ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)(dmac_flush) ); } /* Stop a DMA transfer */ static void astro_dma_stop(struct astro_state *asp) { int j; astro_dma_reset(asp); for (j = 0; j < 3; j++) asp->image_buf[j].state = image_buf_waiting; } /* Start/continue DMA chaining */ static void astro_dma_chain (struct astro_state *asp) { struct image_buf *ip; u_long csr; u_long ar; u_long bcr; u_long readback; if (!asp->busy) { cmn_err(CE_WARN,"astro_dma_chain: not marked busy"); return; } ip = &asp->image_buf[asp->buf_ready]; if ((ip->state == image_buf_ready) && (asp->buf_numbusy < 2)) { csr = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr); if (csr & dmac_na_loaded) { return; } asp->buf_numbusy++; ip->state = image_buf_busy; csr = (dmac_en_next | dmac_en_dma | dmac_faster | dmac_en_cnt | dmac_int_en); if (ip->operation == DDI_DMA_READ) csr |= dmac_write; ar = ip->buf_cookie.dmac_address; bcr = ip->size; ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->bcr, (ulong_t)bcr); ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->ar, (ulong_t)ar); ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)csr); asp->buf_ready++; if (asp->buf_ready == 3) asp->buf_ready = 0; } } /* end astro_dma_chain */ /* * Interrupt handler */ static u_int astro_intr (caddr_t instance) { register struct image_buf *ip; register u_long csr; int j = 0; struct astro_state *asp = (struct astro_state *) ddi_get_soft_state(statep, (u_int)instance); if (asp == NULL) return DDI_INTR_UNCLAIMED; mutex_enter (&asp->mu); ip = &asp->image_buf[asp->buf_busy]; csr = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr); if (!(csr & (dmac_err_pend | dmac_int_pend))) { mutex_exit (&asp->mu); return DDI_INTR_UNCLAIMED; } if (!asp->busy) { ip->det_state = DEVICE_NOT_BUSY; cmn_err (CE_WARN, "astro_intr: device not busy"); } else if (csr & dmac_err_pend) { ip->det_state = DATA_TRANS_ERROR; cmn_err (CE_WARN, "astro_intr: data transfer error csr = 0x%x", csr); } else if (csr & dmac_tc) { if ((asp->buf_numbusy == 0) || (ip->state != image_buf_busy)) { ip->det_state = IMAGE_BUF_NOT_BUSY; goto FLUSH; } if (csr & dmac_write) { csr = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr); while ((csr & dmac_draining) && (j++ < 30)) { csr = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr); drv_usecwait(1); } } if(ip->size == TDL_SIZE) csr = dmac_flush; else if (!(csr & dmac_en_next)) csr = dmac_flush; ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)csr); readback = ddi_getl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr); ip->state = image_buf_done; asp->buf_numbusy--; asp->buf_busy++; if (asp->buf_busy == 3) asp->buf_busy = 0; cv_signal(&asp->bufcv); /* * if we're testing, reset the csr */ if (ip->size == TDL_SIZE) astro_dma_reset(asp); /* * if we're done with a data transfer, disenable the dma and interrupt */ else if ( !(readback & dmac_a_loaded) && ( asp->byteremain == 0 ) ) { csr = readback & ~dmac_en_dma; csr = csr & ~dmac_int_en; ddi_putl(asp->reg_access_handle, (ulong_t *)&asp->regp->csr, (ulong_t)csr); } /* * we're not done with the data transfer, so continue */ else astro_dma_chain(asp); mutex_exit (&asp->mu); return DDI_INTR_CLAIMED; } else { cmn_err (CE_WARN, "astro_intr: bad interrupt encountered csr = 0x%x", csr); ip->det_state = BAD_INTERRUPT; } FLUSH: astro_dma_reset(asp); ip->state = image_buf_error; cv_signal(&asp->bufcv); mutex_exit (&asp->mu); return DDI_INTR_CLAIMED; } /*--------------------8-->----cut--here----<--8--------------------*/ /* * $Log: astro.c,v $ * Revision 1.8 1998/05/08 18:06:14 lopez * Added some comments and made CAPITALIZED some constant names. No new code. * * Revision 1.7 1998/02/18 18:36:50 lopez * Added ASTRO_SET_TIMEOUT to astro_ioctl. Eventhough timeouts are not * used with this version, it is here because of ioctl errors and possible * future upgrades. * * Revision 1.6 1998/02/16 17:46:35 lopez * changed unlong_t ulong_t * * Revision 1.5 1998/02/13 17:43:22 lopez * Added O.S. specific #define's and added an RCS header and Log. * */