/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1990, 1991 UNIX System Laboratories, Inc. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T */ /* All Rights Reserved */ /* Copyright (c) 1988, 1989 Intel Corp. */ /* All Rights Reserved */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Set features for each architecture. List of features: * ADDR_32: Address is 32 bits * COUNT_24: Count is 24 bits * DMA_4CSCD: DMA channel 4 is used for cascade of channels 0-3) * DMA_INTR: DMA interrupt is available (always with DMA_BUF_CHAIN) * DMA_BUF_CHAIN: DMA buffer chaining is available (always with DMA_INTR) * MEM_TO_MEM: Memory to memory transfers available * NO_PROG_WIDTH: Channel data width is NOT programmable * SCATER_GATHER Scatter-gather DMA is available (code not implemented) * ISA_MODE Standard ISA modes available * EISA_EXT_MODE: EISA extension modes available */ /* * Address is 24 bits (default) with no carry between lo word and hi byte * Count is 16 bits (default) */ #define DMA_4CSCD #define NO_PROG_WIDTH #define ISA_MODE #include #include #include #include #include #include #include #if defined(DEBUG) #include static int i8237debug = 0; #define dprintf(x) if (i8237debug) (void)prom_printf x #else #define dprintf(x) #endif /* defined(DEBUG) */ extern int EISA_chaining; /* * data structures for maintaining the DMACs */ static kmutex_t dma_engine_lock; static struct d37A_chan_reg_addr chan_addr[] = { D37A_BASE_REGS_VALUES }; static ushort_t d37A_chnl_path[] = { DMAE_PATH_8, /* first 4 DMA channels default to 8-bit xfers */ DMAE_PATH_8, DMAE_PATH_8, DMAE_PATH_8, 0, DMAE_PATH_16, /* last 3 DMA channels default to 16-bit xfers */ DMAE_PATH_16, DMAE_PATH_16}; static ushort_t d37A_chnl_mode[] = { DMAE_TRANS_SNGL, DMAE_TRANS_SNGL, DMAE_TRANS_SNGL, DMAE_TRANS_SNGL, #ifdef DMA_4CSCD DMAE_TRANS_CSCD, #else /* !DMA_4CSCD */ DMAE_TRANS_SNGL, #endif /* !DMA_4CSCD */ DMAE_TRANS_SNGL, DMAE_TRANS_SNGL, DMAE_TRANS_SNGL}; #ifdef DMA_BUF_CHAIN static ddi_dma_cookie_t *d37A_next_cookie[] = {0, 0, 0, 0, 0, 0, 0, 0}; #endif /* DMA_BUF_CHAIN */ #ifdef DMA_INTR static uint_t d37A_intr(caddr_t); #endif static int d37A_set_mode(struct ddi_dmae_req *, int); static int d37A_write_addr(ulong_t, int); static ulong_t d37A_read_addr(int); static int d37A_write_count(long, int); static long d37A_read_count(int); #ifdef DMA_BUF_CHAIN static void dEISA_setchain(ddi_dma_cookie_t *cp, int chnl); #endif /* * Routine: d37A_init() * purpose: initializes the 8237A. * caller: dma_init() * calls: d37A macros, d37A_init() */ /*ARGSUSED*/ int d37A_init(dev_info_t *dip) { #ifdef DMA_INTR ddi_iblock_cookie_t iblk_cookie = 0; int error; if ((error = ddi_add_intr(dip, (uint_t)0, &iblk_cookie, (ddi_idevice_cookie_t *)0, d37A_intr, (caddr_t)NULL)) != DDI_SUCCESS) { if (error != DDI_INTR_NOTFOUND) cmn_err(CE_WARN, "!d37A_init: cannot add dma intr\n"); EISA_chaining = 0; } mutex_init(&dma_engine_lock, NULL, MUTEX_DRIVER, (void *)iblk_cookie); #else /* !DMA_INTR */ mutex_init(&dma_engine_lock, NULL, MUTEX_DRIVER, NULL); #endif /* !DMA_INTR */ return (DDI_SUCCESS); } /* * Routine: d37A_valid() * purpose: validates the channel to be acquired. * caller: i_dmae_acquire() * calls: */ int d37A_dma_valid(int chnl) { #ifdef DMA_4CSCD if (chnl == 4) return (0); #endif /* DMA_4CSCD */ return (1); } /* * Routine: d37A_release() * purpose: resets the 8237A mode. * caller: i_dmae_free() * calls: */ void d37A_dma_release(int chnl) { #ifdef DMA_4CSCD if (chnl == 4) return; #endif /* DMA_4CSCD */ d37A_chnl_mode[chnl] = DMAE_TRANS_SNGL; } /* * routine: d37A_dma_disable() * purpose: Prevent the DMAC from responding to external hardware * requests for DMA service on the given channel * caller: dma_disable() * calls: d37A macros */ void d37A_dma_disable(int chnl) { dprintf(("d37A_dma_disable: chnl=%d mask_reg=0x%x\n", chnl, chan_addr[chnl].mask_reg)); outb(chan_addr[chnl].mask_reg, (chnl & 3) | DMA_SETMSK); } /* * routine: d37A_dma_enable() * purpose: Enable to DMAC to respond to hardware requests for DMA * service on the specified channel. * caller: dma_enable() * calls: d37A macros */ void d37A_dma_enable(int chnl) { dprintf(("d37A_dma_enable: chnl=%d mask_reg=0x%x val=0x%x\n", chnl, chan_addr[chnl].mask_reg, chnl & 3)); /* mutex_enter(&dma_engine_lock); */ outb(chan_addr[chnl].mask_reg, chnl & 3); /* mutex_exit(&dma_engine_lock); */ } /* * routine: d37A_get_best_mode() * purpose: stub routine - determine optimum transfer method * caller: dma_get_best_mode(). * calls: */ /* ARGSUSED */ uchar_t d37A_get_best_mode(struct ddi_dmae_req *dmaereqp) { return (DMAE_CYCLES_2); } #ifdef DMA_INTR /* * routine: d37A_intr() * purpose: stub routine * caller: * calls: dma_intr(). */ /*ARGSUSED*/ static uint_t d37A_intr(caddr_t arg) { int chnl, istate, nstate; uint_t mask; if ((istate = (inb(EISA_DMAIS) & 0xef)) != 0) { /* channel 4 can't interrupt */ chnl = 0; nstate = istate; mutex_enter(&dma_engine_lock); do { if (istate & 1) { dEISA_setchain(d37A_next_cookie[chnl], chnl); #ifdef DEBUG if (chnl < 4) mask = inb(DMAC1_ALLMASK) >> (chnl); else mask = inb(DMAC2_ALLMASK) >> (chnl - 4); if (mask & 1) prom_printf("eisa: dma buffer chaining failure chnl %d!\n", chnl); #endif /* DEBUG */ } chnl++; istate >>= 1; } while (istate); chnl = 0; do { if ((nstate & 1) && d37A_next_cookie[chnl]) d37A_next_cookie[chnl] = _dmae_nxcookie(chnl); chnl++; nstate >>= 1; } while (nstate); mutex_exit(&dma_engine_lock); return (DDI_INTR_CLAIMED); } return (DDI_INTR_UNCLAIMED); } #endif /* DMA_INTR */ #ifdef DMA_BUF_CHAIN /* * routine: dEISA_setchain() * purpose: Set next buffer address/count from chain * caller: d37A_intr() * calls: d37A macros */ static void dEISA_setchain(ddi_dma_cookie_t *cp, int chnl) { if (cp) { dprintf(("dEISA_setchain: chnl=%d next_addr=%x count=%lx\n", chnl, cp->dmac_address, cp->dmac_size)); (void) d37A_write_addr(cp->dmac_address, chnl); (void) d37A_write_count(cp->dmac_size, chnl); outb(chan_addr[chnl].scm_reg, chnl | EISA_ENCM | EISA_CMOK); } else { /* * clear chain enable bit */ outb(chan_addr[chnl].scm_reg, chnl); dprintf(("dEISA_setchain: chnl=%d end\n", chnl)); } } #endif /* DMA_BUF_CHAIN */ /* * routine: d37A_prog_chan() * purpose: program the Mode registers and the Base registers of a * DMA channel for a subsequent hardware-initiated transfer. * caller: dma_prog_chan() * calls: d37A_write_addr(), d37A_write_count(), d37A macros. */ int d37A_prog_chan(struct ddi_dmae_req *dmaereqp, ddi_dma_cookie_t *cp, int chnl) { if (d37A_chnl_mode[chnl] == DMAE_TRANS_CSCD) { dprintf(("d37A_prog_chan err: chnl=%d in cascade mode\n", chnl)); return (DDI_FAILURE); } #ifndef MEM_TO_MEM if (dmaereqp && dmaereqp->der_dest == DMAE_DEST_MEM) { dprintf(("d37A_prog_chan err: memory to memory mode not supported.\n")); return (DDI_FAILURE); } #endif /* !MEM_TO_MEM */ dprintf(("d37A_prog_chan: chnl=%d dmaereq=%p\n", chnl, (void *)dmaereqp)); if (dmaereqp) { switch (chnl) { case DMAE_CH0: case DMAE_CH1: case DMAE_CH2: case DMAE_CH3: #ifdef NO_PROG_WIDTH if (dmaereqp->der_path && dmaereqp->der_path != DMAE_PATH_8) { dprintf(("d37A_prog_chan err: chnl %d not programmed.\n", chnl)); return (DDI_FAILURE); } #endif /* NO_PROG_WIDTH */ break; #ifndef DMA_4CSCD case DMAE_CH4: #endif /* !DMA_4CSCD */ case DMAE_CH5: case DMAE_CH6: case DMAE_CH7: #ifdef NO_PROG_WIDTH if (dmaereqp->der_path && dmaereqp->der_path != DMAE_PATH_16) { dprintf(("d37A_prog_chan err: chnl %d not programmed.\n", chnl)); return (DDI_FAILURE); } #endif /* NO_PROG_WIDTH */ break; default: dprintf(("d37A_prog_chan err: chnl %d not programmed.\n", chnl)); return (DDI_FAILURE); } } else chnl &= 3; mutex_enter(&dma_engine_lock); d37A_dma_disable(chnl); if (dmaereqp) (void) d37A_set_mode(dmaereqp, chnl); if (cp) { (void) d37A_write_addr(cp->dmac_address, chnl); (void) d37A_write_count(cp->dmac_size, chnl); #ifdef DMA_BUF_CHAIN if (dmaereqp && dmaereqp->der_bufprocess == DMAE_BUF_CHAIN && (d37A_next_cookie[chnl] = _dmae_nxcookie(chnl))) { /* * i/o operation has more than 1 cookie * so enable dma buffer chaining */ drv_usecwait(10); outb(chan_addr[chnl].scm_reg, chnl | EISA_ENCM); drv_usecwait(15); dEISA_setchain(d37A_next_cookie[chnl], chnl); d37A_next_cookie[chnl] = _dmae_nxcookie(chnl); } #endif /* DMA_BUF_CHAIN */ } mutex_exit(&dma_engine_lock); return (DDI_SUCCESS); } /* * routine: d37A_dma_swsetup() * purpose: program the Mode registers and the Base register for the * specified channel. * caller: dma_swsetup() * calls: d37A_write_addr(), d37A_write_count(), d37A macros. */ int d37A_dma_swsetup(struct ddi_dmae_req *dmaereqp, ddi_dma_cookie_t *cp, int chnl) { if (d37A_chnl_mode[chnl] == DMAE_TRANS_CSCD) { dprintf(("d37A_dma_swsetup err: chnl %d not programmed\n", chnl)); return (DDI_FAILURE); } dprintf(("d37A_dma_swsetup: chnl=%d dmaereq=%p.\n", chnl, (void *)dmaereqp)); /* MUST BE IN BLOCK MODE FOR SOFTWARE INITIATED REQUESTS */ if (dmaereqp->der_trans != DMAE_TRANS_BLCK) dmaereqp->der_trans = DMAE_TRANS_BLCK; switch (chnl) { case DMAE_CH0: case DMAE_CH1: case DMAE_CH2: case DMAE_CH3: #ifdef NO_PROG_WIDTH if (dmaereqp->der_path && dmaereqp->der_path != DMAE_PATH_8) { dprintf(("d37A_dma_swsetup err: chnl %d not programmed.\n", chnl)); return (DDI_FAILURE); } #endif /* NO_PROG_WIDTH */ break; #ifndef DMA_4CSCD case DMAE_CH4: #endif /* !DMA_4CSCD */ case DMAE_CH5: case DMAE_CH6: case DMAE_CH7: #ifdef NO_PROG_WIDTH if (dmaereqp->der_path && dmaereqp->der_path != DMAE_PATH_16) { dprintf(("d37A_dma_swsetup err: chnl %d not programmed.\n", chnl)); return (DDI_FAILURE); } #endif /* NO_PROG_WIDTH */ break; default: dprintf(("d37A_dma_swsetup err: chnl %d not set up.\n", chnl)); return (DDI_FAILURE); }; mutex_enter(&dma_engine_lock); d37A_dma_disable(chnl); (void) d37A_set_mode(dmaereqp, chnl); (void) d37A_write_addr(cp->dmac_address, chnl); (void) d37A_write_count(cp->dmac_size, chnl); #ifdef DMA_BUF_CHAIN if (dmaereqp->der_bufprocess == DMAE_BUF_CHAIN && (d37A_next_cookie[chnl] = _dmae_nxcookie(chnl))) { /* * i/o operation has more than 1 cookie * so enable dma buffer chaining */ outb(chan_addr[chnl].scm_reg, chnl | EISA_ENCM); dEISA_setchain(d37A_next_cookie[chnl], chnl); d37A_next_cookie[chnl] = _dmae_nxcookie(chnl); } #endif /* DMA_BUF_CHAIN */ mutex_exit(&dma_engine_lock); return (DDI_SUCCESS); } /* * routine: d37A_dma_swstart() * purpose: SW start transfer setup on the indicated channel. * caller: dma_swstart() * calls: d37A_dma_enable(), d37A macros */ void d37A_dma_swstart(int chnl) { dprintf(("d37A_dma_swstart: chnl=%d\n", chnl)); mutex_enter(&dma_engine_lock); d37A_dma_enable(chnl); outb(chan_addr[chnl].reqt_reg, DMA_SETMSK | chnl); /* set request bit */ mutex_exit(&dma_engine_lock); } /* * routine: d37A_dma_stop() * purpose: Stop any activity on the indicated channel. * caller: dma_stop() * calls: d37A macros */ void d37A_dma_stop(int chnl) { dprintf(("d37A_dma_stop: chnl=%d\n", chnl)); mutex_enter(&dma_engine_lock); d37A_dma_disable(chnl); outb(chan_addr[chnl].reqt_reg, chnl & 3); /* reset request bit */ mutex_exit(&dma_engine_lock); } /* * routine: d37A_get_chan_stat() * purpose: retrieve the Current Address and Count registers for the * specified channel. * caller: dma_get_chan_stat() * calls: d37A_read_addr(), d37A_read_count(). */ void d37A_get_chan_stat(int chnl, ulong_t *addressp, int *countp) { ulong_t taddr; int tcount; mutex_enter(&dma_engine_lock); taddr = d37A_read_addr(chnl); tcount = d37A_read_count(chnl); mutex_exit(&dma_engine_lock); if (addressp) *addressp = taddr; if (countp) *countp = tcount; dprintf(("d37A_get_chan_stat: chnl=%d address=%lx count=%x\n", chnl, taddr, tcount)); } /* * routine: d37A_set_mode() * purpose: program the Mode registers of the * DMAC for a subsequent hardware-initiated transfer. * caller: d37A_prog_chan(), d37A_dma_swsetup * calls: */ static int d37A_set_mode(struct ddi_dmae_req *dmaereqp, int chnl) { uchar_t mode = 0, emode = 0; #ifdef ISA_MODE #if defined(lint) emode = emode; #endif mode = chnl & 3; switch (dmaereqp->der_command) { case DMAE_CMD_READ: mode |= DMAMODE_READ; break; case DMAE_CMD_WRITE: mode |= DMAMODE_WRITE; break; case DMAE_CMD_VRFY: mode |= DMAMODE_VERF; break; case DMAE_CMD_TRAN: mode |= 0x0C; /* for Adaptec 1st party DMA on chnl 0 */ break; default: return (DDI_FAILURE); } if (dmaereqp->der_bufprocess == DMAE_BUF_AUTO) mode |= DMAMODE_AUTO; if (dmaereqp->der_step == DMAE_STEP_DEC) mode |= DMAMODE_DECR; switch (dmaereqp->der_trans) { case DMAE_TRANS_SNGL: mode |= DMAMODE_SINGLE; break; case DMAE_TRANS_BLCK: mode |= DMAMODE_BLOCK; break; case DMAE_TRANS_DMND: break; case DMAE_TRANS_CSCD: mode |= DMAMODE_CASC; break; default: return (DDI_FAILURE); } d37A_chnl_mode[chnl] = dmaereqp->der_trans; dprintf(("d37A_set_mode: chnl=%d mode_reg=0x%x mode=0x%x\n", chnl, chan_addr[chnl].mode_reg, mode)); outb(chan_addr[chnl].mode_reg, mode); #endif /* ISA_MODE */ #ifdef EISA_EXT_MODE emode = chnl & 3; d37A_chnl_path[chnl] = dmaereqp->der_path; switch (dmaereqp->der_path) { case DMAE_PATH_8: /* emode |= EISA_DMA_8; */ break; case DMAE_PATH_16: emode |= EISA_DMA_16; break; case DMAE_PATH_32: emode |= EISA_DMA_32; break; case DMAE_PATH_16B: emode |= EISA_DMA_16B; break; default: switch (chnl) { case DMAE_CH0: case DMAE_CH1: case DMAE_CH2: case DMAE_CH3: d37A_chnl_path[chnl] = DMAE_PATH_8; /* emode |= EISA_DMA_8; */ break; case DMAE_CH5: case DMAE_CH6: case DMAE_CH7: d37A_chnl_path[chnl] = DMAE_PATH_16; emode |= EISA_DMA_16; break; } } emode |= (dmaereqp->der_cycles & 3) << 4; outb(chan_addr[chnl].emode_reg, emode); dprintf(("d37A_set_mode: chnl=%d em_reg=0x%x emode=0x%x\n", chnl, chan_addr[chnl].emode_reg, emode)); #endif /* EISA_EXT_MODE */ return (DDI_SUCCESS); } /* * routine: d37A_write_addr() * purpose: write the 24- or 32-bit physical address into the Base Address * Register for the indicated channel. * caller: d37A_prog_chan(), d37A_dma_swsetup(). * calls: d37A macros */ static int d37A_write_addr(ulong_t paddress, int chnl) { uchar_t *adr_byte; dprintf(("d37A_write_addr: chnl=%d address=%lx\n", chnl, paddress)); switch (d37A_chnl_path[chnl]) { case DMAE_PATH_8: case DMAE_PATH_16B: case DMAE_PATH_32: /* * program DMA controller with byte address */ break; case DMAE_PATH_16: /* * convert byte address to shifted word address */ paddress = (paddress & ~0x1ffff) | ((paddress & 0x1ffff) >> 1); break; default: return (DDI_FAILURE); } kpreempt_disable(); /* don't preempt thread while using flip-flop */ outb(chan_addr[chnl].ff_reg, 0); /* set flipflop */ adr_byte = (uchar_t *)&paddress; outb(chan_addr[chnl].addr_reg, adr_byte[0]); outb(chan_addr[chnl].addr_reg, adr_byte[1]); outb(chan_addr[chnl].page_reg, adr_byte[2]); #ifdef ADDR_32 outb(chan_addr[chnl].hpage_reg, adr_byte[3]); #endif /* ADDR_32 */ kpreempt_enable(); return (DDI_SUCCESS); } /* * routine: d37A_read_addr() * purpose: read the 24- or 32-bit physical address from the Current Address * Register for the indicated channel. * caller: d37A_get_chan_stat(). * calls: d37A macros */ static ulong_t d37A_read_addr(int chnl) { ulong_t paddress = 0; uchar_t *adr_byte; kpreempt_disable(); /* don't preempt thread while using flip-flop */ adr_byte = (uchar_t *)&paddress; outb(chan_addr[chnl].ff_reg, 0); /* set flipflop */ adr_byte[0] = inb(chan_addr[chnl].addr_reg); adr_byte[1] = inb(chan_addr[chnl].addr_reg); adr_byte[2] = inb(chan_addr[chnl].page_reg); #ifdef ADDR_32 adr_byte[3] = inb(chan_addr[chnl].hpage_reg); #endif /* ADDR_32 */ kpreempt_enable(); switch (d37A_chnl_path[chnl]) { case DMAE_PATH_8: case DMAE_PATH_16B: case DMAE_PATH_32: /* * return with byte address */ break; case DMAE_PATH_16: /* * convert shifted word address to byte address */ paddress = (paddress & ~0x1ffff) | ((paddress & 0x0ffff) << 1); break; default: return ((ulong_t)DDI_FAILURE); } dprintf(("d37A_read_addr: chnl=%d address=%lx.\n", chnl, paddress)); return (paddress); } /* * routine: d37A_write_count() * purpose: write the 16- or 24-bit count into the Base Count Register for * the indicated channel. * caller: d37A_prog_chan(), d37A_dma_swsetup() * calls: d37A macros */ static int d37A_write_count(long count, int chnl) { uchar_t *count_byte; dprintf(("d37A_write_count: chnl=%d count=0x%lx\n", chnl, count)); switch (d37A_chnl_path[chnl]) { case DMAE_PATH_16: /* * Convert byte count to word count */ count >>= 1; /* FALLTHROUGH */ case DMAE_PATH_8: case DMAE_PATH_16B: case DMAE_PATH_32: --count; break; default: return (DDI_FAILURE); } kpreempt_disable(); /* don't preempt thread while using flip-flop */ outb(chan_addr[chnl].ff_reg, 0); /* set flipflop */ count_byte = (uchar_t *)&count; outb(chan_addr[chnl].cnt_reg, count_byte[0]); outb(chan_addr[chnl].cnt_reg, count_byte[1]); #ifdef COUNT_24 outb(chan_addr[chnl].hcnt_reg, count_byte[2]); #endif /* COUNT_24 */ kpreempt_enable(); return (DDI_SUCCESS); } /* * routine: d37A_read_count() * purpose: read the 16- or 24-bit count from the Current Count Register for * the indicated channel * caller: d37A_get_chan_stat() * calls: d37A macros */ static long d37A_read_count(int chnl) { long count = 0; uchar_t *count_byte; kpreempt_disable(); /* don't preempt thread while using flip-flop */ count_byte = (uchar_t *)&count; outb(chan_addr[chnl].ff_reg, 0); /* set flipflop */ count_byte[0] = inb(chan_addr[chnl].cnt_reg); count_byte[1] = inb(chan_addr[chnl].cnt_reg); #ifdef COUNT_24 count_byte[2] = inb(chan_addr[chnl].hcnt_reg); #endif /* COUNT_24 */ #ifdef COUNT_24 if ((ulong_t)count == 0xffffff) #else /* !COUNT_24 */ if ((ulong_t)count == 0xffff) #endif /* !COUNT_24 */ count = -1; kpreempt_enable(); switch (d37A_chnl_path[chnl]) { case DMAE_PATH_8: case DMAE_PATH_16B: case DMAE_PATH_32: ++count; break; case DMAE_PATH_16: /* * Convert incremented word count to byte count */ count = (count + 1) << 1; break; } dprintf(("d37A_read_count: chnl=%d count=0x%lx\n", chnl, count)); return (count); }