/*- * * Copyright (c) Andrew Ancel Gray ancelgray "AT" y a h o o "DOT" com * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (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 AMD CS5536 sound driver / audio driver is derived from * Bruce R. Montegue's AMD CS5530 audio driver and the Linux CS5535 ALSA driver, * * This driver was developed on FreeBSD 6.2 with an ALIX-1C motherboard * with an AMD Geode LX cpu and a CS5536 companion chip. CS5535's are known * to be similar, but this code does not support it yet. * * This driver has 2 Play channels and 1 Record channel. Play Chan0 and Play Chan1 * use the same bus master controller, so they are tied together with the same: * 1) Sample Rate, 2) Blocksize, 3) Mono mode. * Channel 0 takes precedence in setting these values. * Chan0/Chan1 can be run simultaneously in Mono mode, but if stereo mode is * desired, only Chan0 will sound. Chan1 can only be Mono. * * Note that the default in FreeBSD's mixer program is "rec=0:0" * * So before the mic will work, the record level must be set. Run: * * "mixer rec 75", for default.. Or: "mixer rec 100" for maximum. * * Alternatively, put: * * hint.pcm.0.rec="75" in /boot/device.hints * * * Also, on FreeBSD 7.0, vhans defaults to ENABLED. This driver is not * very compatible with vchans, so one must turn them off to get this * driver to work correctly. Run: * * sysctl dev.pcm.0.play.vchans=0 * sysctl dev.pcm.0.rec.vchans=0 * * Or, put * * dev.pcm.0.play.vchans=0 * dev.pcm.0.rec.vchans=0 * * into the /etc/sysctl.conf file. * * All three channels can be run simultaneously in Mono Mode with no problem. * Chan0 will set the rate and blocksize for Chan1 and Chan0. * * A program that needs to set these parameters and use both play channels * should open /dev/dsp first in mono mode write (default) to get Chan0 play. * Set the rate and blocksize for Channel 0. * THEN, open /dev/dsp again in mono mode write to get Chan1 play. * Rate and blocksize for this Chan1 will follow Chan0. * * Record Channel 2 is independent. Open /dev/dsp in stereo/mono mode read * to get Channel 2 record. * In mono record mode, the Left Channel Record inputs (even words in the buffer) * are used, and the Right Channel Record inputs (odd words in buffer) are ignored. * */ #include #include #include #include #include #include #include SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/amd5536.c,v 1.00 2008/06/17 06:10:42 ariff Exp $"); /* -------------------------------------------------------------------- */ #define AMD_BUFSIZE_MIN 128 #define AMD_BUFSIZE_MAX 1024*8 #define AMD_BUFSIZE_DEFAULT 256 #define AMD_CONVERSIONSIZE_MAX 0x10000 /* 64k */ #define AMD_PCHANS 2 /* create /dev/dsp0.[0-N] to use more than one */ #define AMD_RCHANS 1 enum {CHANGE=0, CALL=1, INTR=2, BORING=3, NONE=-1}; #ifndef AMD_DEBUG_LEVEL #define AMD_DEBUG_LEVEL NONE #endif #define AMD_DEBUG(level, _msg) {if ((level) <= AMD_DEBUG_LEVEL) {printf _msg;}} #define FREEBSD7 /* -------------------------------------------------------------------- */ enum { AMD_CS5535, AMD_CS5536 }; static struct amd_card_type { u_int32_t pci_id; int which; char *name; } amd_card_types[] = { { 0xFFFFFFFF, AMD_CS5535, "AMD CS5535" }, /* dont know 5535 id */ { 0x20931022, AMD_CS5536, "AMD CS5536" }, { 0, 0, NULL } }; /* * NOTE: I have no CS5335 hardware, * but I put 5335 in here to be added later. AA Gray. */ struct sc_info; struct sc_chinfo { int ch_num; /* Channel number, does NOT match DMA engine number.*/ int ch_run; /* 1=DMA in progress.*/ int ch_was_running; /* DMA was aborted when last suspend occured. */ u_int32_t ch_speed; /* Channel speed.*/ u_int32_t ch_fmt; /* Channel format.*/ struct sc_info *ch_sc; /* Backptr to our driver's private device info. */ struct pcm_channel *ch_channel; /* PCM channel that generated buffer. */ struct snd_dbuf *ch_snd_dbuf; /* PCM Control struct for buffer to be DMAed. */ /* DMA blocks are in 1 buf attached to snd_dbuf */ u_int32_t ch_num_blks; /* Number of DMA blocks in buffer. */ u_int32_t ch_blk_size; /* Size of each block. An interrupt is triggered. */ /* at the end of each block. */ u_int32_t *ch_dtbl; /* Start PRD descriptor for this channel. */ int ch_cur_dma_blk; /* Num of blk just DMAed (or being DMAed). */ /*----- Below are bus master specific for this channel. */ u_int32_t ch_last_prd_dma_adr; /* Last PRD DMA address of operational DMA. */ u_int32_t ch_dma_cmd; /* Offset of DMA_CMD PCI F3BAR audio register. */ u_int32_t ch_dma_status; /* " DMA STATUS register. */ u_int32_t ch_dma_prd_adr; /* " address of DMA PRD register, contains */ /* physcial adr of next PRD to process. */ int ch_dir; /* CH_INPUT (REC),CH_OUTPUT (PLAY)*/ }; struct sc_info { device_t dev; u_int32_t type; int which; bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t sc_prd_dtag; /* PRD descriptor table */ bus_dma_tag_t sc_conversion_dtag; /* Conversion buffer */ bus_dmamap_t sc_prd_dmap; /* PRD descriptor table */ bus_dmamap_t sc_conversion_dmap; /* Conversion buffer */ struct resource *reg; struct resource *irq; int regtype; int regid; int irqid; void *ih; struct sc_chinfo sc_ch[3]; /* Priv Channel info for STEREO PLAYREC,MONO PLAYREC * Channel 0 Can be stereo or mono play * Channel 1 Can only be mono play IF ch0 is not stereo play * Channel 2 Can be stereo or mono Record */ unsigned int sc_bufsz; struct mtx *sc_lock; struct ac97_info *sc_ac97_codec; int sc_ch_num; u_int8_t *sc_dtbl; /* Single PRD descriptor table, divided for all channels.*/ /* (this is the virtual address, not physical.)*/ u_int8_t *sc_conversionbuf; /* monostereo/stereomono conversion bufs * for play and for record * if just one mono play channel is active, * then we simply take the mono snd_buf words * and stuff them into the even words of the * conversion buf, zeroing out the odd words. * if two mono play channels are active, then * we will be interleaving them "manually" * into the even and odd words. (L & R channels) * * For mono record, we will take * the even words out of the conversion buf and * stuff them into the mono snd_bufs. */ }; /* static global arguments for our assembly routines */ static int numloops0; static int numloops1; static int numloops2; static int numloops3; static int numloops4; static int numloops5; static u_int8_t *srcbuf0; static u_int8_t *srcbuf1; static u_int8_t *srcbuf2; static u_int8_t *srRbuf2; static u_int8_t *srcbuf3; static u_int8_t *srcbuf4; static u_int8_t *srcbuf5; static u_int8_t *dstbuf0; static u_int8_t *dstbuf1; static u_int8_t *dstbuf2; static u_int8_t *dstbuf3; static u_int8_t *dstbuf4; static u_int8_t *dstbuf5; #define AMD_LOCK(_sc) snd_mtxlock((_sc)->sc_lock) #define AMD_UNLOCK(_sc) snd_mtxunlock((_sc)->sc_lock) #define AMD_LOCK_ASSERT(_sc) snd_mtxassert((_sc)->sc_lock) /* -------------------------------------------------------------------- */ /* play channel interface */ static void *amd_pchan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int amd_pchan_free(kobj_t, void *); static int amd_pchan_setformat(kobj_t, void *, u_int32_t); static int amd_pchan_setspeed(kobj_t, void *, u_int32_t); static int amd_pchan_setblocksize(kobj_t, void *, u_int32_t); static int amd_pchan_trigger(kobj_t, void *, int); static u_int32_t amd_pchan_getptr(kobj_t, void *); static struct pcmchan_caps *amd_pchan_getcaps(kobj_t, void *); static struct pcmchan_caps *amd_rchan_getcaps(kobj_t, void *); /* talk to the codec - called from ac97.c */ static int amd_initcd(kobj_t, void *); static int amd_rdcd(kobj_t, void *, int); static int amd_wrcd(kobj_t, void *, int, u_int32_t); /* stuff */ static void amd_intr(void *); static int amd_init(struct sc_info *); static void amd_codec_reset(struct sc_info *); /*---- @@ Prototypes. ------------------------------------------------------*/ static void amd_start_dma( struct sc_chinfo * ); static void amd_stop_dma( struct sc_chinfo * ); static void stereo_prd_filldtbl( struct sc_chinfo * ); static void mono_prd_filldtbl( struct sc_chinfo * ); static void set_codec_rates( struct sc_info * ); static void set_codec_playrate( struct sc_info *, int ); static void set_codec_recordrate( struct sc_info *, int ); /* Mono/Stereo assembly routines */ static void movemono2stereo0( void ); static void movemono2stereo1( void ); static void movemono2stereo2( void ); static void movestereo2mono3( void ); static void movemono2stereo4( void ); static void movemono2stereo5( void ); /* -------------------------------------------------------------------- */ /* Codec descriptor */ static kobj_method_t amd_codec_methods[] = { KOBJMETHOD(ac97_init, amd_initcd), KOBJMETHOD(ac97_read, amd_rdcd), KOBJMETHOD(ac97_write, amd_wrcd), { 0, 0 } }; AC97_DECLARE(amd_codec); /* -------------------------------------------------------------------- */ /* channel descriptors */ static u_int32_t amd_playfmt[] = { AFMT_S16_LE, /* Signed, 16-bit, little endian */ AFMT_STEREO | AFMT_S16_LE, /* Note: Unsigned to be obsoleted */ 0 }; static u_int32_t amd_mono_playfmt[] = { AFMT_S16_LE, /* Signed, 16-bit, little endian */ 0 /* No stereo here. Must be mono */ }; static u_int32_t amd_recfmt[] = { AFMT_S16_LE, /* Signed, 16-bit, little endian */ AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps amd_playcaps = {8000, 48000, amd_playfmt, 0}; static struct pcmchan_caps amd_mono_playcaps = {8000, 48000, amd_mono_playfmt, 0}; static struct pcmchan_caps amd_reccaps = {8000, 48000, amd_recfmt, 0}; static struct pcmchan_caps * amd_pchan_getcaps(kobj_t kobj, void *chdata) { return( &amd_playcaps ); } static struct pcmchan_caps * amd_pmono_chan_getcaps( kobj_t obj, void *data ) { /* printf( "pchan getcaps\n" ); */ return( &amd_mono_playcaps ); } static struct pcmchan_caps * amd_rchan_getcaps(kobj_t kobj, void *chdata) { return( &amd_reccaps ); } static kobj_method_t amd_pchan_methods[] = { KOBJMETHOD(channel_init, amd_pchan_init), KOBJMETHOD(channel_free, amd_pchan_free), KOBJMETHOD(channel_setformat, amd_pchan_setformat), KOBJMETHOD(channel_setspeed, amd_pchan_setspeed), KOBJMETHOD(channel_setblocksize, amd_pchan_setblocksize), KOBJMETHOD(channel_trigger, amd_pchan_trigger), KOBJMETHOD(channel_getptr, amd_pchan_getptr), KOBJMETHOD(channel_getcaps, amd_pchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(amd_pchan); static kobj_method_t amd_pmono_chan_methods[] = { KOBJMETHOD(channel_init, amd_pchan_init), KOBJMETHOD(channel_free, amd_pchan_free), KOBJMETHOD(channel_setformat, amd_pchan_setformat), KOBJMETHOD(channel_setspeed, amd_pchan_setspeed), KOBJMETHOD(channel_setblocksize, amd_pchan_setblocksize), KOBJMETHOD(channel_trigger, amd_pchan_trigger), KOBJMETHOD(channel_getptr, amd_pchan_getptr), KOBJMETHOD(channel_getcaps, amd_pmono_chan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(amd_pmono_chan); /* Instantiates "amd_rmono_chan_class" CHANNEL class,*/ /* using "amd_rmono_chan_methods".*/ static kobj_method_t amd_rchan_methods[] = { KOBJMETHOD(channel_init, amd_pchan_init), /* Note we use pchan stuff */ KOBJMETHOD(channel_free, amd_pchan_free), /* for rchan */ KOBJMETHOD(channel_setformat, amd_pchan_setformat), KOBJMETHOD(channel_setspeed, amd_pchan_setspeed), KOBJMETHOD(channel_setblocksize, amd_pchan_setblocksize), KOBJMETHOD(channel_trigger, amd_pchan_trigger), KOBJMETHOD(channel_getptr, amd_pchan_getptr), KOBJMETHOD(channel_getcaps, amd_rchan_getcaps), /* except getcaps */ { 0, 0 } }; CHANNEL_DECLARE(amd_rchan); /* -------------------------------------------------------------------- */ /* some i/o convenience functions */ #define amd_rd_1(sc, regno) bus_space_read_1(sc->st, sc->sh, regno) #define amd_rd_2(sc, regno) bus_space_read_2(sc->st, sc->sh, regno) #define amd_rd_4(sc, regno) bus_space_read_4(sc->st, sc->sh, regno) #define amd_wr_1(sc, regno, data) bus_space_write_1(sc->st, sc->sh, regno, data) #define amd_wr_2(sc, regno, data) bus_space_write_2(sc->st, sc->sh, regno, data) #define amd_wr_4(sc, regno, data) bus_space_write_4(sc->st, sc->sh, regno, data) static __inline int amd_wait_cmd(struct sc_info *sc) { int i; u_int32_t data32; for (i=0 ; i<20 ; i++) { data32 = amd_rd_4( sc, ACC_CODEC_CNTL ); if( !(data32 & CMD_NEW) ) { /* printf("Codec Ready for Command\n"); */ return 0; } DELAY(2); } return -1; } static __inline int amd_wait_status(struct sc_info *sc) { int i; u_int32_t data32; for (i=0 ; i<20 ; i++) { data32 = amd_rd_4( sc, ACC_CODEC_STATUS ); if( data32 & STATUS_NEW ) { /* printf("Codec Ready for Status Read\n"); */ return 0; } DELAY(2); } printf( "Error: Wait Status timed out\n" ); return -1; } /* -------------------------------------------------------------------- */ /* ac97 codec */ static int amd_initcd(kobj_t kobj, void *devinfo) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data32=0; AMD_DEBUG(CALL, ("amd_initcd\n")); /* init ac-link */ data32 = amd_rd_4( sc, ACC_CODEC_STATUS ); if( data32 & PRIMARY_RDY ) { return(1); } else { device_printf( sc->dev, "Initcd Codec Not Ready\n" ); return(0); } /* * return the number of codecs: Assume only 1 codec on stereo busmasters 0 and 1. * The cs5536 supports multiple busmasters, but they are routed to non-existant * secondary slave codecs for the ALIX-1C motherboard, i.e., it has only 1 codec. * It would be nice to have access to secondary mono codecs natively. * For example, secondary codec 10 (binary) would support mono natively in ac97 slots 6&9 (busmasters 5 & 7) * See DSA assignments in ac97 register 0x28, and codec pins ID0,ID1 on pins 45 & 46. */ return(1); } static int amd_rdcd(kobj_t kobj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t data32=0; u_int32_t Data32=0; if (amd_wait_cmd(sc)) { device_printf(sc->dev, "amd_wait_cmd timed out.\n"); return -1; } data32 |= regno << 24; data32 |= 0x80000000; /* Set bit 31 to read */ data32 |= CMD_NEW; amd_wr_4( sc, ACC_CODEC_CNTL, data32 ); DELAY(50); /* ac97 cycle = 20.8 usec */ if (amd_wait_status(sc)) { device_printf(sc->dev, "amd_wait_status timed out.\n"); return -1; } Data32 = amd_rd_4(sc, ACC_CODEC_STATUS); /* printf( " Rd:%X Reg:%X ", Data32, regno ); */ return( Data32 & 0xFFFF ); } static int amd_wrcd(kobj_t kobj, void *devinfo, int regno, u_int32_t data) { struct sc_info *sc = (struct sc_info *)devinfo; u_int32_t datatemp=0; u_int32_t data32= 0; if (amd_wait_cmd(sc)) { device_printf(sc->dev, "amd_wait_cmd timed out.\n"); return -1;; } data32 |= regno << 24; data32 &= 0x7FFFFFFF; /* Clr bit 31 to write */ data32 |= CMD_NEW; datatemp = data & 0x0000FFFF; /* Data is only 16 bits */ data32 |= datatemp; amd_wr_4( sc, ACC_CODEC_CNTL, data32 ); /* printf("Codec Write: %X Reg:%X\n", data32, regno ); */ DELAY(50); /* ac97 cycle = 20.8 usec */ return 0; } /* -------------------------------------------------------------------- */ /* play channel interface */ static void * amd_pchan_init( kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir ) { struct sc_info *sc = devinfo; struct sc_chinfo *ch; int num; num = sc->sc_ch_num; /* For each channel instantiation,*/ (sc->sc_ch_num)++; if( num>3 ) { /* error condition */ device_printf( sc->dev, "Error in chan_init\n" ); return(NULL); } /* printf( " [pchan]init %X ", num ); */ ch = &sc->sc_ch[num]; /* point to our private channel data.*/ ch->ch_sc = sc; /* Save backpointer to our private driver data,*/ ch->ch_snd_dbuf = b; /* attach control buffer struct,*/ ch->ch_channel = c; /* and ptr to PCM channel struct.*/ ch->ch_num = num; ch->ch_run = 0; /* Get start of our PRD array.*/ ch->ch_dtbl = (u_int32_t *)sc->sc_dtbl + (num * (NUM_PRD_ELEMS * 2 * sizeof(u_int32_t)) ); ch->ch_num_blks = 2; /* Default is always double-buffering, */ ch->ch_blk_size = sc->sc_bufsz / 2; /* User can change sc_bufsz */ /* From pcm_get_buffersize() in "_attach()". */ ch->ch_cur_dma_blk = 0; /* Current DMA target. */ ch->ch_fmt = AFMT_S16_LE; /* default format signed16 Little Endian */ ch->ch_speed = DSP_DEFAULT_SPEED; /* default speed 8000 Hz. */ /* printf( "DEFAULT_SPEED: %X\n", DSP_DEFAULT_SPEED ); */ /* Allocate a data buffer for DMA using associated DMA tag.*/ #ifdef FREEBSD7 if ( sndbuf_alloc( ch->ch_snd_dbuf, /* For this control buffer */ sc->sc_prd_dtag, 0, /* using this TAG (mem rqmts), dmaflags */ sc->sc_bufsz ) == -1) /* Allocate this size buffer. */ return( NULL ); #else if ( sndbuf_alloc( ch->ch_snd_dbuf, /* For this control buffer */ sc->sc_prd_dtag, /* using this TAG (mem rqmts), */ sc->sc_bufsz ) == -1) /* Allocate this size buffer. */ return( NULL ); #endif /* printf( " num:%X sc->sc_bufsz = %X ", num, sc->sc_bufsz ); */ mono_prd_filldtbl( ch ); /* if ch_num==1, then dma is already set up, * channel 0 was already init'ed, * channel 1 must piggyback onto channel 0 * and have the same block size and the * same rate. * So we do nothing if ch_num==1. */ ch->ch_last_prd_dma_adr = -1; /* No DMA adr yet.*/ /* Channel num does NOT correspond to fixed-function bus master (DMA engine). * * Channel 0: Mono/Stereo Play Bus Master 0 Play Channel (index 0) * Mono Play * Stereo Play Can only have 2 Mono play channels * OR 1 Stereo play channel. * * If mono Right play Channel 1 is active, * and STEREO play channel 0 starts, * Mono Right Channel 1 will not be heard. * * * Channel 1: Mono Play Bus Master 0 2nd Mono Channel (index 1) * Shares Bus Master 0 with Channel 0 * * Channel 2: Record Bus Master 1 1 mono rec channel (index 2) * Mono Rec OR 1 stereo rec channel (index 2 also ) * Stereo Rec * * * * Buffer setup for Play and Record: * * controlled by: * snd_amd5536.ko * |-------------| 0x10000 * | | * | | * | | * | | * | Channel 2 | * | conversion1 | * | | * | | * | | * controlled by: | | * sound.ko | DMA:YES | * |-------------| +? |-------------| +0x8000 + 2*ch->ch_blk_size * | | | | * | Channel 2 | | | * | snd_buf1 | | | * | | | | * | DMA:YES | | Channel 2 | * |-------------| +? | conversion0 | * | | | | * | Channel 2 | | | * | snd_buf0 | | | * | | | | * | DMA:YES | | DMA:YES | * |=============| +? |=============| +0x8000 * | | | | * | Channel 1 | | | * | snd_buf1 | | | * | | | | * | DMA:NO | | Channel 0/1 | * |-------------| +? | conversion1 | * | | | | * | Channel 1 | | | * | snd_buf0 | | | * | | | | * | DMA:NO | | DMA:YES | * |=============| +? (controlled |-------------| + 2*ch->ch_blk_size * | | by sound.ko) | | * | Channel 0 | | | * | snd_buf1 | | | * | | | | * | DMA:YES | | Channel 0/1 | * |-------------| +ch->ch_blk_size | conversion0 | * | | | | * | Channel 0 | | | * | snd_buf0 | | | * | | | | * | DMA:YES | | DMA:YES | * |-------------| +0 |-------------| +0 * 3 pairs 2 pairs * */ switch( num ) { /* Mono or Stero Play Channel 0: 16bit or 16bit*2 output */ case 0: ch->ch_dir = CH_OUTPUT; ch->ch_dma_cmd = ACC_BM0_CMD; /* Offset of DMA_CMD audio reg. */ ch->ch_dma_status = ACC_BM0_STATUS; /* " DMA STATUS reg. */ ch->ch_dma_prd_adr = ACC_BM0_PRD; /* " address of DMA PRD table. */ break; /* Mono Play only Channel 1, 16-bit, output. */ case 1: ch->ch_dir = CH_OUTPUT; ch->ch_dma_cmd = ACC_BM0_CMD; /* Offset of DMA_CMD audio reg. */ ch->ch_dma_status = ACC_BM0_STATUS; /* " DMA STATUS reg. */ ch->ch_dma_prd_adr = ACC_BM0_PRD; /* " address of DMA PRD table. */ break; case 2: /* Mono or Stero Record Channel 2: 16bit or 16bit*2 input */ ch->ch_dir = CH_INPUT; ch->ch_dma_cmd = ACC_BM1_CMD; /* Offset of DMA_CMD audio reg. */ ch->ch_dma_status = ACC_BM1_STATUS; /* " DMA STATUS reg. */ ch->ch_dma_prd_adr = ACC_BM1_PRD; /* " address of DMA PRD table. */ break; } return( ch ); } static int amd_pchan_free(kobj_t obj, void *data ) { struct sc_chinfo *ch = data; /* Our channel */ /* printf( " pchan free " ); */ sndbuf_free( ch->ch_snd_dbuf ); return( 0 ); /* OK */ } /* * Start audio PCI "DMA engines" (bus masters). each channel, except Chan1, has a * dedicated bus master. * * A bus master is controlled by 3 io-mapped regs in the PCI audio function. * These are a PRD table address register, a command register, and a status register. * * The DMA status register (one for each bus master) is read-to-clear, so the first * time bit 0x01 (End of Page) is read, it is cleared. * *--- * * DMA Start. We usually start at the 1st PRD descriptor, after which DMA * loops forever. However, if Chan0 is already active and Chan1 starts, * then we may not start at the 1st descriptor (or vice versa). * ch_cur_dma_blk tracks the block being DMAed, so on a * completion trigger it points to the "finished" block. * * If Ch0 or Ch1 is already running, we do not have * to restart the dma for Ch0/Ch1 * */ /*-----------------------------------------------------------------------*/ static void amd_start_dma( struct sc_chinfo *ch ) { struct sc_info *sc; struct sc_chinfo *ch0; struct sc_chinfo *ch1; u_int32_t prd_phy_adr; u_int8_t status; sc = ch->ch_sc; ch0 = &sc->sc_ch[0]; ch1 = &sc->sc_ch[1]; /* printf( "!!start DMA, ch->ch_num=%x \n", ch->ch_num ); */ if( ch->ch_dir == CH_OUTPUT ) { if( !ch0->ch_run && !ch1->ch_run ) /* Neither Ch0/Ch1 running */ status = amd_rd_1( sc, ch->ch_dma_status ); /* DMA Status, Paranoid Read clears EOP flgs*/ } else /* CH_INPUT */ status = amd_rd_1( sc, ch->ch_dma_status ); /* DMA Status, Paranoid Read clears EOP flgs*/ prd_phy_adr = (u_int32_t)vtophys( ch->ch_dtbl ); if( ch->ch_dir == CH_OUTPUT ) { if( !ch0->ch_run && !ch1->ch_run ) /* Neither Ch0/Ch1 running */ amd_wr_4( sc, ch->ch_dma_prd_adr, prd_phy_adr ); } else amd_wr_4( sc, ch->ch_dma_prd_adr, prd_phy_adr ); ch->ch_cur_dma_blk = 0; ch->ch_last_prd_dma_adr = prd_phy_adr; if( ch->ch_dir == CH_OUTPUT ) { if( !ch0->ch_run && !ch1->ch_run ) { amd_wr_1( sc, ch->ch_dma_cmd, 0x01 ); /* printf(" Wrote BM start command \n" ); */ } } else if( ch->ch_dir == CH_INPUT ) { amd_wr_1( sc, ch->ch_dma_cmd, 0x09 ); /* printf(" Wrote BM start command \n" ); */ } ch->ch_run = 1; } /* * Stop a bus master; called on abort and at end of overall DMA operation * (that is, when there is no more data to be transfered). * * If ch0 & ch1 play are BOTH running, then we dont need * to stop dma if one quits. * * Chan 2 Record runs independently */ /*-----------------------------------------------------------------------*/ static void amd_stop_dma( struct sc_chinfo *ch ) { struct sc_info *sc; struct sc_chinfo *ch0; struct sc_chinfo *ch1; u_int8_t status,cmd; u_int32_t prd_phy_adr; sc = ch->ch_sc; ch0 = &sc->sc_ch[0]; ch1 = &sc->sc_ch[1]; status = amd_rd_1( sc, ch->ch_dma_status ); /* DMA Status, Paranoid Read clears EOP flgs*/ cmd = amd_rd_1( sc, ch->ch_dma_cmd ); /* DMA CMD. */ cmd &= 0xFE; /* Clear enable bit. */ if( ch->ch_dir == CH_OUTPUT ) { if( !(ch0->ch_run && ch1->ch_run) ) { /* Not both running */ amd_wr_1( sc, ch->ch_dma_cmd, cmd ); /* DMA CMD. Write Clear enable bit. */ /* printf(" Wrote BM stop cmd Ch%d \n", ch->ch_num ); */ } else if( ch0->ch_run && ch1->ch_run && ch->ch_num==0 ) { /* both Chan's running and Channel 0 to be turned off */ if( ch0->ch_fmt & AFMT_STEREO ) { /* Channel 1 is running silently behind stero channel 1 * So we turn it back on since Channel 0 is going off. */ printf( "Turning Channel 1 back on\n" ); mono_prd_filldtbl( ch1 ); prd_phy_adr = (u_int32_t)vtophys( ch->ch_dtbl ); amd_wr_4( sc, ch->ch_dma_prd_adr, prd_phy_adr ); ch->ch_cur_dma_blk = 0; ch->ch_last_prd_dma_adr = prd_phy_adr; amd_wr_1( sc, ch->ch_dma_cmd, cmd ); /* Turn DMA off momentarily */ amd_wr_1( sc, ch->ch_dma_cmd, 0x01 ); /* Turn back on */ ch0->ch_fmt = AFMT_S16_LE; /* Put channel 0 back to mono */ mono_prd_filldtbl( ch0 ); } } } else if( ch->ch_dir == CH_INPUT ) { amd_wr_1( sc, ch->ch_dma_cmd, cmd ); /* DMA CMD. Write Clear enable bit. */ /* printf(" Wrote BM stop cmd %d \n", ch->ch_num ); */ } ch->ch_run = 0; } /* * Construct a 3-element PRD table to control bus-engine DMA. The first 2 PRD entries each * point to countiguous data buffers, while the last PRD entry points back to the first * PRD entry. The bus-engine thus runs in a loop. The EOP flag in the 2 data buffer * descriptors causes an interrupt when the corresponding DMA is complete. The * two buffers provide a double-buffering scheme in conjunction with the PCM driver. */ /*-----------------------------------------------------------------------*/ static void stereo_prd_filldtbl( struct sc_chinfo *ch ) { u_int32_t base; base = vtophys( sndbuf_getbuf( ch->ch_snd_dbuf )); /* Get memory physical address */ /* of DMA buffer. */ ch->ch_num_blks = 2; ch->ch_blk_size = sndbuf_getsize( ch->ch_snd_dbuf ) / 2 ; /* * printf( " fill: ch_num:%X blk_size=%d. mem:%X ", ch->ch_num, * ch->ch_blk_size, (unsigned int)sndbuf_getbuf( ch->ch_snd_dbuf ) ); */ ch->ch_dtbl[0] = base; ch->ch_dtbl[1] = ( 0x40000000 | ch->ch_blk_size); /* EOP (DMA completion interrupt)*/ ch->ch_dtbl[2] = base + (1 * ch->ch_blk_size); ch->ch_dtbl[3] = ( 0x40000000 | ch->ch_blk_size); /* EOP (DMA completion interrupt)*/ /* * Make the last entry "loop" back to the first descriptor. * No interr+upt or DMA buffer is associated with this entry. */ ch->ch_dtbl[4] = vtophys( ch->ch_dtbl ); ch->ch_dtbl[5] = 0x20000000; /* JMP (loop back to top of PRD descriptor table.)*/ /* NOTE: EOT and JMP bit cannot both be set,*/ /* if JMP, no EOT required.*/ } /* * Construct a different 3-element PRD table to control bus-engine DMA. The first 2 PRD entries each * point to countiguous data buffers, while the last PRD entry points back to the first * PRD entry. The bus-engine thus runs in a loop. The EOP flag in the 2 data buffer * descriptors causes an interrupt when the corresponding DMA is complete. The * two buffers provide a double-buffering scheme in conjunction with the PCM driver. */ /*-----------------------------------------------------------------------*/ static void mono_prd_filldtbl( struct sc_chinfo *ch ) { u_int32_t base; struct sc_info *sc; sc = ch->ch_sc; if( ch->ch_dir == CH_OUTPUT ) base = vtophys( sc->sc_conversionbuf ); /* Get memory physical address */ /* Get memory physical address */ else base = vtophys( sc->sc_conversionbuf + 0x8000 ); /* Get memory physical address */ /* Get memory physical address */ ch->ch_num_blks = 2; ch->ch_blk_size = sndbuf_getsize( ch->ch_snd_dbuf ) / 2 ; /* This is the block size that * sound.ko is using. * If in mono mode, then we also * have some memory buffers * which are twice this size */ /* * printf( " fill: ch_num:%X blk_size=%d. mem:%X ", ch->ch_num, * ch->ch_blk_size, (unsigned int)sndbuf_getbuf( ch->ch_snd_dbuf ) ); */ ch->ch_dtbl[0] = base; ch->ch_dtbl[1] = ( 0x40000000 | (2*ch->ch_blk_size)); /* EOP (DMA completion interrupt)*/ ch->ch_dtbl[2] = base + (2 * ch->ch_blk_size); ch->ch_dtbl[3] = ( 0x40000000 | (2*ch->ch_blk_size)); /* EOP (DMA completion interrupt)*/ /* * Make the last entry "loop" back to the first descriptor. * No interr+upt or DMA buffer is associated with this entry. */ ch->ch_dtbl[4] = vtophys( ch->ch_dtbl ); ch->ch_dtbl[5] = 0x20000000; /* JMP (loop back to top of PRD descriptor table.)*/ /* NOTE: EOT and JMP bit cannot both be set,*/ /* if JMP, no EOT required.*/ } /* * The "channel_setblocksize()" method returns the number of bytes expected * to be processed by a single DMA operation, that is, bytes processed * between DMA completion interrupts. This value will evenly divide * the total length of the buffer associated with the channel. * * This routine should return the closest possible block size less than * or equal to the input blocksize. This method will not be called when * the channel is running. */ /*-----------------------------------------------------------------------*/ static int amd_pchan_setblocksize( kobj_t obj, void *data, u_int32_t blocksize ) { struct sc_chinfo *ch = data; struct sc_chinfo *ch0; struct sc_info *sc; u_int32_t tmpblocksize; tmpblocksize = blocksize; if( blocksize > AMD_BUFSIZE_MAX ) blocksize = AMD_BUFSIZE_MAX; if( blocksize < 128 ) blocksize = 128; /* printf( " blocksize=%d", blocksize ); */ if( ch->ch_fmt & AFMT_STEREO ) { ch->ch_blk_size = blocksize; stereo_prd_filldtbl( ch ); /* Write default DMA descriptors.*/ } /* Normal operation, point busmaster * to sound.ko's snd_bufs */ else { /* mono */ if( ch->ch_num != 1 ) ch->ch_blk_size = blocksize; else { /* Channel 1 */ sc = ch->ch_sc; /* Can't set block size on channel 1 */ ch0 = &sc->sc_ch[0]; /* It must be the same as channel 0 */ ch->ch_blk_size = ch0->ch_blk_size; blocksize = ch0->ch_blk_size; } mono_prd_filldtbl( ch ); /* Mono/Stereo conversion mode * point busmasters to * conversion buffers */ } if( tmpblocksize != blocksize ) sndbuf_resize( ch->ch_snd_dbuf, 2, blocksize ); return( blocksize ); } static int amd_pchan_setformat(kobj_t kobj, void *data, u_int32_t format) { struct sc_chinfo *ch = data; /* printf( " Ch%d: setf: %8X ", ch->ch_num, format ); */ if( ch->ch_num != 1 ) /* Channel 1 is AMFT_S16_LE only */ ch->ch_fmt = format; else ch->ch_fmt = AFMT_S16_LE; /* just making sure */ if( ch->ch_fmt & AFMT_STEREO ) { stereo_prd_filldtbl( ch ); /* Write default DMA descriptors.*/ /* Normal operation, point busmaster * to sound.ko's snd_bufs */ } else { mono_prd_filldtbl( ch ); /* Mono/Stereo conversion mode * point busmasters to * conversion buffers */ } // put filldtbl here! /* return 0 - format _must_ be settable if in getcaps list */ /* dont have to do anything since it is in list */ /* must return 0 */ /* won't be called while channel running */ return( 0 ); } /* * Set the codec speed (rate) for a given channel. * Note: Play Channel 1 follows Play Channel 0. */ /*-----------------------------------------------------------------------*/ static int amd_pchan_setspeed( kobj_t obj, void *data, u_int32_t speed ) { struct sc_chinfo *ch; struct sc_chinfo *ch0; struct sc_info *sc; /* printf( "\n[pchan]setspeed\n" ); */ ch = data; sc = ch->ch_sc; if( ch->ch_dir == CH_INPUT ) /* Set Record Channel 2 */ set_codec_recordrate( sc, speed ); else { if( ch->ch_num != 1 ) /* Can only set Play Channel 0 */ set_codec_playrate( sc, speed ); } if( ch->ch_num == 0 || ch->ch_num == 2 ) /* Only Channels 0,2 were set */ ch->ch_speed = speed; else if( ch->ch_num == 1 ) { sc=ch->ch_sc; ch0 = &sc->sc_ch[0]; ch->ch_speed = ch0->ch_speed; /* Channel 1 follows Channel 0 */ } return( speed ); } /* * The PCM driver, when it has output data to play or requires input * data, uses the channel buffers that have already been setup. * * To start playing, the PCM driver will fill both buffers * associated with the channel (that is, all snd_bufs), and * then will call the "channel_trigger" method with a "go" code of * PCMTRIG_START. The trigger method should start DMA operation. * An interrupt occurs after "ch_block_size" bytes have been DMAed * (that is, after one block in the buffer has been DMAed). The * first block to be DMAed (and interrupt at completion) will * usually be block 0. We then set up the next block to be DMA'ed * * For recording, the PCM driver will simply take the dma'ed * record buffers one at a time as they occur during an interrupt. * * On PCMTRIG_START, the DMA transfer to be activated is specified * as block "sndbuf_getbuf()" with size "sndbuf_getsize()". */ /*-----------------------------------------------------------------------*/ static int amd_pchan_trigger( kobj_t obj, void *data, int go ) { struct sc_chinfo *ch = data; struct sc_info *sc; struct sc_chinfo *ch0; struct sc_chinfo *ch1; int mono_offsetL, mono_offsetR, stereo_offset; sc = ch->ch_sc; ch0 = &sc->sc_ch[0]; ch1 = &sc->sc_ch[1]; /* printf( " pchan trigger, go=%x\n", go ); */ switch(go) { case PCMTRIG_START: /* if mono, we need mono2stereo conversion on first 2 blocks * given to us by sound.ko */ if( !(ch->ch_fmt & AFMT_STEREO) ) { /* Mono */ if( ch->ch_dir == CH_OUTPUT ) { /* Mono Play Channel */ if( ch==ch0 ) { /* channel 0, so we transfer both of sound.ko's buffers * into the even (Left Channel) words of the conversion buf. * If the current buf==0, then we transfer into buf 0, then 1. * if the current buf==1, then we transfer into buf 1, then 0; */ /* printf( " Ch0 " ); */ /* Sync memory from cache */ bus_dmamap_sync( sc->sc_conversion_dtag, sc->sc_conversion_dmap, BUS_DMASYNC_PREWRITE ); /* move first buffer */ numloops0 = (ch->ch_blk_size)/(2*8); /* load 2 bytes at a time, */ /* 8 instruction groups in loop */ stereo_offset = 0; mono_offsetL = 0; if ( ch1->ch_run && ch1->ch_cur_dma_blk == 1 ) { stereo_offset = ch->ch_cur_dma_blk * ch->ch_blk_size*2; mono_offsetL = ch->ch_cur_dma_blk * ch->ch_blk_size; } dstbuf0 = sc->sc_conversionbuf + stereo_offset; srcbuf0 = (u_int8_t *)(sndbuf_getbuf( ch->ch_snd_dbuf)) + mono_offsetL; if( !(ch1->ch_run) ) movemono2stereo0(); /* Move with zero out of Right Channel */ else { numloops4 = numloops0; dstbuf4 = dstbuf0; srcbuf4 = srcbuf0; movemono2stereo4(); /* Move without zero out of Right Channel */ } /* move 2nd buffer */ numloops0 = (ch->ch_blk_size)/(2*8); /* load 2 bytes at a time, */ /* 8 instruction groups in loop */ stereo_offset = ch->ch_cur_dma_blk * ch->ch_blk_size*2; mono_offsetL = ch->ch_cur_dma_blk * ch->ch_blk_size; if ( ch1->ch_run && ch1->ch_cur_dma_blk == 1 ) { stereo_offset = 0; mono_offsetL = 0; } dstbuf0 = sc->sc_conversionbuf + stereo_offset; srcbuf0 = (u_int8_t *)(sndbuf_getbuf( ch->ch_snd_dbuf)) + mono_offsetL; if( !(ch1->ch_run) ) movemono2stereo0(); /* Move with zero out of Right Channel */ else { numloops4 = numloops0; dstbuf4 = dstbuf0; srcbuf4 = srcbuf0; movemono2stereo4(); /* Move without zero out of Right Channel */ } /* Sync memory from cache */ bus_dmamap_sync( sc->sc_conversion_dtag, sc->sc_conversion_dmap, BUS_DMASYNC_POSTWRITE ); } else if( ch==ch1 ) { /* channel, so we transfer the 2 buffers * into the odd (Right Channel) words of the conversion buf. * If the current buf=0, then we transfer into buf 0, then 1. * if the current buf=1, then we transfer into buf 1, then 0; */ /* printf( " Ch1 " ); */ /* Sync memory from cache */ bus_dmamap_sync( sc->sc_conversion_dtag, sc->sc_conversion_dmap, BUS_DMASYNC_PREWRITE ); /* move first buffer */ numloops1 = (ch->ch_blk_size)/(2*8); /* load 2 bytes at a time, */ /* 8 instruction groups in loop */ stereo_offset = 0; mono_offsetR = 0; if ( ch0->ch_run && ch0->ch_cur_dma_blk == 1 ) { stereo_offset = ch->ch_cur_dma_blk * ch->ch_blk_size*2; mono_offsetR = ch->ch_cur_dma_blk * ch->ch_blk_size; } dstbuf1 = sc->sc_conversionbuf + stereo_offset; srcbuf1 = (u_int8_t *)(sndbuf_getbuf( ch->ch_snd_dbuf)) + mono_offsetR; if( !(ch0->ch_run) ) movemono2stereo1(); /* Move with zero out of Left Channel */ else { numloops5 = numloops1; dstbuf5 = dstbuf1; srcbuf5 = srcbuf1; movemono2stereo5(); /* Move without zero out of Left Channel */ } /* move 2nd buffer */ numloops1 = (ch->ch_blk_size)/(2*8); /* load 2 bytes at a time, */ /* 8 instruction groups in loop */ stereo_offset = ch->ch_cur_dma_blk * ch->ch_blk_size*2; mono_offsetR = ch->ch_cur_dma_blk * ch->ch_blk_size; if ( ch0->ch_run && ch0->ch_cur_dma_blk == 1 ) { stereo_offset = 0; mono_offsetR = 0; } dstbuf1 = sc->sc_conversionbuf + stereo_offset; srcbuf1 = (u_int8_t *)(sndbuf_getbuf( ch->ch_snd_dbuf)) + mono_offsetR; if( !(ch0->ch_run) ) movemono2stereo1(); /* Move with zero out of Left Channel */ else { numloops5 = numloops1; dstbuf5 = dstbuf1; srcbuf5 = srcbuf1; movemono2stereo5(); /* Move without zero out of Left Channel */ } /* Sync memory from cache */ bus_dmamap_sync( sc->sc_conversion_dtag, sc->sc_conversion_dmap, BUS_DMASYNC_POSTWRITE ); } else if( ch0->ch_run && ch1->ch_run ) { /* This should never happen, since we are just now triggering one play channel */ device_printf( sc->dev, "Error, triggering an already-started-channel" ); } } else /* if( ch->ch_dir == CH_INPUT ) */ { /* Mono Record: Don't have to do anything here */ /* printf( "Mono Record Trigger\n" ); */ } } amd_start_dma( ch ); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: /* stop operation */ amd_stop_dma( ch ); break; default: break; } return( 0 ); /* OK */ } /* * Get an OFFSET (not a pointer despite the name) to the next channel * block that the PCM driver is to fill (after DMA completion on play * channels), or copy (after DMA completion on record channels). * * The "channel_getptr()" method is called by the PCM driver's * "chn_intr()" routine, which is called by this driver's interrupt * routine, "amd_intr()". The "channel_getptr()" routine should * return the offset of the data block (within the overall channel * buffer) that the PCM driver is to process _next_. For a * double-bufferd channel, this routine first returns the offset * of buffer 0, then 1, then 0, etc. When a play channel is started, * all blocks are filled before the first DMA is started. */ /*-----------------------------------------------------------------------*/ static u_int32_t amd_pchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch; int pos; /* struct sc_info *sc = ch->ch_sc; */ /* printf( "getptr\n" ); */ ch = data; pos = ch->ch_cur_dma_blk * ch->ch_blk_size; return( pos ); /* return current byte offset of channel.*/ } /*-----------------------------------------------------------------------*\ * @@@ The interrupt handler. *--------------------------- * * Interrupt notification is by device (PCU audio function), not * channel, so the interrupt routine needs to figure out what channel * caused the interrupt. When the interrupt routine runs, multiple * channels may be ready for additional work. * * This routine is responsible for updating the channel's "current" * DMA buffer indicator (the "ch_cur_dma_blk" index). The PCM driver * determines which DMA buffer it should read or write by calling * the "channel_getptr()" method when the interrupt routine calls * "chn_intr()". Thus, buffer bookkeeping should happen after the * "chn_intr()" call in the following interrupt code. * * The PRD DMA descriptor table that corresponds to the data buffers * never needs to change (the DMA engine just keeps processing it * "in a circle"). The interrupt routine uses the change in the * "processed PRD address" contained in the memory-mapped audio * function registers of the appropriate bus-master to detect the * end of a "DMA" operation. * * The EOP bit needs to be read after DMA completion, or the bus * master (and associated transfer) will halted on "double EOP * write". */ /*-----------------------------------------------------------------------*/ static void amd_intr( void *p ) { struct sc_info *sc; struct sc_chinfo *ch; struct sc_chinfo *ch0; struct sc_chinfo *ch1; struct sc_chinfo *ch2; u_int32_t acc_irq_stat; u_int16_t bm_stat; unsigned char count; int mono_offsetL, mono_offsetR, stereo_offset; sc = (struct sc_info *)p; ch0 = &sc->sc_ch[0]; ch1 = &sc->sc_ch[1]; ch2 = &sc->sc_ch[2]; AMD_LOCK(sc); /* Read IRQ status register to see which busmaster needs service */ acc_irq_stat = amd_rd_4( sc, ACC_IRQ_STATUS ); if (!acc_irq_stat) { /* acc_irq_stat = amd_rd_4( sc, IRQ_STS ); Read GPIO irq status? */ /* No need to, we turned GPIO irqs off */ AMD_UNLOCK(sc); return; /*no irqs to process, interrupts are shared on PCI bus. Not ours. */ } /* printf( " i%X ", acc_irq_stat ); */ for (count=0; count < 10; count++) { if (acc_irq_stat & (1<ch_dma_status ); /* Clear EOP.*/ if( ch0->ch_fmt & AFMT_STEREO ) { /* Channel 0 Set for Stereo Play */ if( ch0->ch_run ) { ch = ch0; if (bm_stat & BM_EOP_ERR) { device_printf(sc->dev, "bm0 bus master error.\n" ); } if (bm_stat & EOP) { AMD_UNLOCK(sc); chn_intr( ch->ch_channel ); /* PCM generic int routine.*/ AMD_LOCK(sc); /* Interrupt has been processed, update pointer to next DMA buffer.*/ (ch->ch_cur_dma_blk)++; if (ch->ch_cur_dma_blk >= ch->ch_num_blks ) { ch->ch_cur_dma_blk = 0; } } } if( ch1->ch_run ) { /* This should not happen very often * that channel 1 tries to continue to run mono after * channel 0 has been reprogrammed to stereo. * In this case, Ch1 does not output sound. * * But we still must cycle Ch1's running buffers in any case. * */ /* printf( "Channel 1 Mono Run while Channel 0 is Stereo\n" ); */ ch = ch1; if (bm_stat & BM_EOP_ERR) { device_printf(sc->dev, "bm0 bus master error.\n" ); } if (bm_stat & EOP) { AMD_UNLOCK(sc); chn_intr( ch->ch_channel ); /* PCM generic int routine.*/ AMD_LOCK(sc); numloops1 = (ch->ch_blk_size)/(2*8); /* load 2 bytes at a time, 8 instruction groups in loop */ stereo_offset = ch->ch_cur_dma_blk * ch->ch_blk_size*2; mono_offsetR = ch->ch_cur_dma_blk * ch->ch_blk_size; dstbuf1 = sc->sc_conversionbuf + stereo_offset; srcbuf1 = (u_int8_t *)(sndbuf_getbuf( ch->ch_snd_dbuf)) + mono_offsetR; movemono2stereo1(); /* Interrupt has been processed, update pointer to next DMA buffer.*/ (ch->ch_cur_dma_blk)++; if (ch->ch_cur_dma_blk >= ch->ch_num_blks ) { ch->ch_cur_dma_blk = 0; } } } } else if( !(ch0->ch_fmt & AFMT_STEREO) ) { /* Channel 0 Set for Mono Play */ if( ch0->ch_run && !(ch1->ch_run) ) { /* Channel 0 mono by itself */ ch = ch0; //bm_stat = amd_rd_1( sc, ch->ch_dma_status ); /* Clear EOP.*/ if (bm_stat & BM_EOP_ERR) { device_printf(sc->dev, "bm0 bus master error.\n" ); } if (bm_stat & EOP) { AMD_UNLOCK(sc); /* sound.ko moves next block into "empty" buffer */ chn_intr( ch->ch_channel ); /* PCM generic int routine.*/ AMD_LOCK(sc); /* Now we must do mono2stero conversion, */ /* from sound.ko's mono buffer to our stereo buffer's even words */ numloops0 = (ch->ch_blk_size)/(2*8); /* load 2 bytes at a time, 8 instruction groups in loop */ stereo_offset = ch->ch_cur_dma_blk * ch->ch_blk_size*2; mono_offsetL = ch->ch_cur_dma_blk * ch->ch_blk_size; dstbuf0 = sc->sc_conversionbuf + stereo_offset; srcbuf0 = (u_int8_t *)(sndbuf_getbuf( ch->ch_snd_dbuf)) + mono_offsetL; movemono2stereo0(); /* Interrupt has been processed, update pointer to next DMA buffer.*/ (ch->ch_cur_dma_blk)++; if (ch->ch_cur_dma_blk >= ch->ch_num_blks ) { ch->ch_cur_dma_blk = 0; } } } else if( !(ch0->ch_run) && ch1->ch_run ) { /* Channel 1 mono by itself */ ch = ch1; //bm_stat = amd_rd_1( sc, ch->ch_dma_status ); /* Clear EOP.*/ if (bm_stat & BM_EOP_ERR) { device_printf(sc->dev, "bm0 bus master error.\n" ); } if (bm_stat & EOP) { AMD_UNLOCK(sc); /* sound.ko moves next block into "empty" buffer */ chn_intr( ch->ch_channel ); /* PCM generic int routine.*/ AMD_LOCK(sc); /* Now we must do mono2stero conversion, */ /* from sound.ko's mono buffer to our stereo buffer's even words */ numloops1 = (ch->ch_blk_size)/(2*8); /* load 2 bytes at a time, 8 instruction groups in loop */ stereo_offset = ch->ch_cur_dma_blk * ch->ch_blk_size*2; mono_offsetR = ch->ch_cur_dma_blk * ch->ch_blk_size; dstbuf1 = sc->sc_conversionbuf + stereo_offset; srcbuf1 = (u_int8_t *)(sndbuf_getbuf( ch->ch_snd_dbuf)) + mono_offsetR; movemono2stereo1(); /* Interrupt has been processed, update pointer to next DMA buffer.*/ (ch->ch_cur_dma_blk)++; if (ch->ch_cur_dma_blk >= ch->ch_num_blks ) { ch->ch_cur_dma_blk = 0; } } } else if( ch0->ch_run && ch1->ch_run ) { /* Ch 0 mono and Ch 1 mono simultaneously */ ch = ch0; //bm_stat = amd_rd_1( sc, ch->ch_dma_status ); /* Clear EOP.*/ if (bm_stat & BM_EOP_ERR) { device_printf(sc->dev, "bm0 bus master error.\n" ); } if (bm_stat & EOP) { AMD_UNLOCK(sc); /* sound.ko moves next block into "empty" buffer */ chn_intr( ch->ch_channel ); /* PCM generic int routine.*/ chn_intr( ch1->ch_channel ); /* PCM generic int routine.*/ AMD_LOCK(sc); /* Now we must do mono2stero conversion, */ /* from both of sound.ko's mono bufs to our stereo buf */ numloops2 = (ch->ch_blk_size)/(2*8); /* load 2 bytes at a time, 8 instructions in loop */ stereo_offset = ch->ch_cur_dma_blk * ch->ch_blk_size*2; mono_offsetL = ch->ch_cur_dma_blk * ch->ch_blk_size; mono_offsetR = ch1->ch_cur_dma_blk * ch->ch_blk_size; /* ch1 blksz=ch0 blksz */ dstbuf2 = sc->sc_conversionbuf + stereo_offset; srcbuf2 = (u_int8_t *)(sndbuf_getbuf( ch->ch_snd_dbuf)) + mono_offsetL; srRbuf2 = (u_int8_t *)(sndbuf_getbuf( ch1->ch_snd_dbuf)) + mono_offsetR; movemono2stereo2(); /* Interrupt has been processed, update pointer to next DMA buffer.*/ (ch->ch_cur_dma_blk)++; (ch1->ch_cur_dma_blk)++; if (ch->ch_cur_dma_blk >= ch->ch_num_blks ) { ch->ch_cur_dma_blk = 0; } if (ch1->ch_cur_dma_blk >= ch1->ch_num_blks ) { ch1->ch_cur_dma_blk = 0; } } } } break; case BM1_IRQ_STS: /* Record */ if( ch2->ch_fmt & AFMT_STEREO ) { /* Stereo Record */ ch = ch2; bm_stat = amd_rd_1( sc, ch->ch_dma_status ); /* Clear EOP.*/ if (bm_stat & BM_EOP_ERR) { device_printf(sc->dev, "bm1 bus master error.\n" ); } if (bm_stat & EOP) { AMD_UNLOCK(sc); chn_intr( ch->ch_channel ); /* PCM generic int routine.*/ AMD_LOCK(sc); /* Interrupt has been processed, update pointer to next DMA buffer.*/ (ch->ch_cur_dma_blk)++; if (ch->ch_cur_dma_blk >= ch->ch_num_blks ) { ch->ch_cur_dma_blk = 0; } } } else if( !(ch2->ch_fmt & AFMT_STEREO) ) { /* Mono Record */ ch = ch2; bm_stat = amd_rd_1( sc, ch->ch_dma_status ); /* Clear EOP.*/ if (bm_stat & BM_EOP_ERR) { device_printf(sc->dev, "bm1 bus master error.\n" ); } if (bm_stat & EOP) { /* Now we must do stero2mono conversion, */ /* from our stero buffer's even words to sound.ko's mono buffer */ numloops3 = (ch->ch_blk_size)/(2*8); /* store 2 bytes at a time, 8 instruction groups in loop */ stereo_offset = ch->ch_cur_dma_blk * ch->ch_blk_size*2; mono_offsetL = ch->ch_cur_dma_blk * ch->ch_blk_size; srcbuf3 = sc->sc_conversionbuf + 0x8000 + stereo_offset; dstbuf3 = (u_int8_t *)(sndbuf_getbuf( ch->ch_snd_dbuf)) + mono_offsetL; movestereo2mono3(); AMD_UNLOCK(sc); /* sound.ko gets mono block from "full" buffer */ chn_intr( ch->ch_channel ); /* PCM generic int routine.*/ AMD_LOCK(sc); /* Interrupt has been processed, update pointer to next DMA buffer.*/ (ch->ch_cur_dma_blk)++; if (ch->ch_cur_dma_blk >= ch->ch_num_blks ) { ch->ch_cur_dma_blk = 0; } } } break; case BM2_IRQ_STS: bm_stat = amd_rd_2( sc, ACC_BM2_STATUS); break; case BM3_IRQ_STS: bm_stat = amd_rd_2( sc, ACC_BM3_STATUS); break; case BM4_IRQ_STS: bm_stat = amd_rd_2( sc, ACC_BM4_STATUS); break; case BM5_IRQ_STS: bm_stat = amd_rd_2( sc, ACC_BM5_STATUS); break; case BM6_IRQ_STS: bm_stat = amd_rd_2( sc, ACC_BM6_STATUS); break; case BM7_IRQ_STS: bm_stat = amd_rd_2( sc, ACC_BM7_STATUS); break; default: device_printf(sc->dev, "Unexpected irq src.\n"); break; } } } AMD_UNLOCK(sc); } /* * The following two routines are callback functions for bus_dmamem_load() */ static void prd_callback(void *arg, bus_dma_segment_t *segs, int nseg, int error) { if( error ) printf("Err: PRDbuf DMA Callback, function error. Not Supposed to Happen.\n" ); if( nseg != 1 ) printf("Err: PRDbuf DMA Callback, nsegs not equal to 1. Not Supposed to Happen.\n" ); return; } static void conversion_callback(void *arg, bus_dma_segment_t *segs, int nseg, int error) { if( error ) printf("Err: CNVbuf DMA Callback, function error. Not Supposed to Happen.\n" ); if( nseg != 1 ) printf("Err: CNVbuf DMA Callback, nsegs not equal to 1. Not Supposed to Happen.\n" ); return; } static void set_codec_rates( struct sc_info *sc ) { u_int32_t b2; /* Enable Variable Rates.*/ b2 = amd_rdcd( NULL, sc, 0x2A ); /* we don't have to pass kobj, arg 1, not used */ b2 |= 0x0001; /* VRA on (can now change speed). */ b2 |= 0x0008; /* VRM on ( Mic change speed ); */ amd_wrcd( NULL, sc, 0x2A, b2 ); /* Set to default 8000khz : DSP_DEFAULT_RATE */ /* We dont use the defined DSP_DEFAULT_RATE below to make sure 8000khz */ amd_wrcd( NULL, sc, 0x2C, 8000 ); /* 8000 Hz D/A rate.*/ amd_wrcd( NULL, sc, 0x2E, 8000 ); /* 8000 Hz D/A rate.*/ amd_wrcd( NULL, sc, 0x30, 8000 ); /* 8000 Hz D/A rate.*/ amd_wrcd( NULL, sc, 0x32, 8000 ); /* 8000 Hz A/D rate.*/ amd_wrcd( NULL, sc, 0x34, 8000 ); /* 8000 Hz D/A rate.*/ } static void set_codec_playrate( struct sc_info *sc, int rate ) { u_int32_t b2; /* Enable Variable Rate Audio (VRA) and and set rate */ b2 = amd_rdcd( NULL, sc, 0x2A ); /*we don't have to pass kobj, arg 1, not used */ b2 |= 0x0001; /* VRA on (can now change speed).*/ b2 |= 0x0008; /* VRM on ( Mic change speed ); */ amd_wrcd( NULL, sc, 0x2A, b2 ); amd_wrcd( NULL, sc, 0x2C, rate ); /* rate Hz D/A rate.*/ } static void set_codec_recordrate( struct sc_info *sc, int rate ) { u_int32_t b2; /* Enable Variable Rate Audio (VRA) and and set rate */ b2 = amd_rdcd( NULL, sc, 0x2A ); /*we don't have to pass kobj, arg 1, not used */ b2 |= 0x0001; /* VRA on (can now change speed).*/ b2 |= 0x0008; /* VRM on ( Mic change speed ); */ amd_wrcd( NULL, sc, 0x2A, b2 ); amd_wrcd( NULL, sc, 0x32, rate ); /* rate Hz D/A rate.*/ } static int amd_init(struct sc_info *sc) { int byte_size; u_int32_t status; AMD_LOCK_ASSERT(sc); AMD_DEBUG(CHANGE, ("amd_init\n")); amd_codec_reset(sc); /* Get a "MAP" for hardware accessible memory as specified by a valid "TAG". * This MAP describes memory where the PRD table will go. This table contains * PRD descriptors. Each descriptor contains 2 32-bit words, one of which is * a physical h/w mem address of a buffer to be DMAed. * The PRD table is processed autonomously by the amd audio PCI hardware. * The allocated MAP needs to be "loaded" before the memory can actually be used. */ if (bus_dmamem_alloc( sc->sc_prd_dtag, /* DMA mem "tag" (rqmts) */ (void **)&sc->sc_dtbl, /* Virtual address of allocated mem. */ BUS_DMA_NOWAIT, /* get it now. */ &sc->sc_prd_dmap) /* Result MAP to use to reference this mem. */ ) { printf("Error with dmamem_alloc\n" ); DELAY( 2000 ); return( ENOSPC ); } byte_size = (sizeof(u_int32_t) * 2) /* Byte size of 1 PRD descritpor. */ * /* multiplied by */ (NUM_PRD_ELEMS * 4); /* Allocate a PRD table for all */ /* 4 channels. */ /* Actually "activate" physical memory buffer for device access via the TAG/MAP. */ /* After this call, sc_dtbl can be used. */ if (bus_dmamap_load( sc->sc_prd_dtag, /* DMA mem TAG (rqmts) */ sc->sc_prd_dmap, /* DMA mem MAP of mem we want. */ sc->sc_dtbl, /* Buffer virtual address. */ byte_size, /* Buffer len. */ prd_callback, sc, /* per buf seg callback/arg. */ 0) ) /* flags. */ { bus_dmamem_free( sc->sc_prd_dtag, (void *)sc->sc_dtbl, sc->sc_prd_dmap ); return( ENOSPC ); } /* printf( "\n\n *** dtbl=%x len=%d. padr=%x\n\n", (int)sc->sc_dtbl, byte_size, vtophys( sc->sc_dtbl ) ); */ /* allocate 64k buffer for mono2stereo/stereo2mono conversion */ if (bus_dmamem_alloc( sc->sc_conversion_dtag, /* DMA mem "tag" (rqmts) */ (void **)&sc->sc_conversionbuf, /* Virtual address of allocated mem. */ BUS_DMA_NOWAIT, /* get it now. */ &sc->sc_conversion_dmap) ) /* Result MAP to use to reference this mem.*/ { printf("Error with dmamem_alloc\n" ); DELAY( 2000 ); return( ENOSPC ); } byte_size = AMD_CONVERSIONSIZE_MAX; /* 64k */ /* Actually "activate" physical memory buffer for device access via the TAG/MAP. */ /* After this call, sc_conversionbuf can be used. */ if (bus_dmamap_load( sc->sc_conversion_dtag, /* DMA mem TAG (rqmts) */ sc->sc_conversion_dmap, /* DMA mem MAP of mem we want. */ sc->sc_conversionbuf, /* Buffer virtual address. */ byte_size, /* Buffer len. */ conversion_callback, sc, /* per buf seg callback/arg. */ 0) ) /* flags. */ { printf( "Error with ConvBuf dmamap_load\n" ); bus_dmamem_free( sc->sc_conversion_dtag, (void *)sc->sc_conversionbuf, sc->sc_conversion_dmap ); DELAY(2000); } memset( sc->sc_conversionbuf, 0, byte_size ); set_codec_rates(sc); /* Turn off GPIO interrupts */ status = amd_rd_4( sc, 0 ); status &= (~0x60000000); amd_wr_4( sc, 0, status ); return 0; } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static int amd_pci_probe(device_t dev) { struct amd_card_type *card; AMD_DEBUG(CALL, ("amd_pci_probe(0x%x)\n", pci_get_devid(dev))); for (card = amd_card_types ; card->pci_id ; card++) { if (pci_get_devid(dev) == card->pci_id) { device_set_desc(dev, card->name); /* device_printf("Found AMD CS5536/5 Audio\n" ); */ return 0; } } return ENXIO; } static int amd_pci_attach(device_t dev) { struct sc_info *sc; u_int32_t data; char status[SND_STATUSLEN]; struct amd_card_type *card; int i; AMD_DEBUG(CALL, ("amd_pci_attach\n")); sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); /* first init to zero */ sc->sc_ac97_codec = 0; sc->reg = 0; sc->ih = 0; sc->irq = 0; sc->sc_prd_dtag = 0; sc->sc_conversion_dtag = 0; sc->sc_lock = 0; sc->sc_ch_num = 0; sc->sc_lock = snd_mtxcreate(device_get_nameunit(dev), "snd_cs5536 softc"); sc->dev = dev; sc->type = pci_get_devid(dev); for (card = amd_card_types ; card->pci_id ; card++) { if (sc->type == card->pci_id) { sc->which = card->which; break; } } data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); sc->regid = PCIR_BAR(0); if (!sc->reg) { sc->regtype = SYS_RES_IOPORT; sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, RF_ACTIVE); } if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); goto bad; } sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq) { device_printf(dev, "unable to allocate interrupt\n"); goto bad; } if (snd_setup_intr(dev, sc->irq, INTR_MPSAFE, amd_intr, sc, &sc->ih)) { device_printf(dev, "unable to setup interrupt\n"); goto bad; } sc->sc_bufsz = pcm_getbuffersize(dev, AMD_BUFSIZE_MIN, AMD_BUFSIZE_DEFAULT, AMD_BUFSIZE_MAX); /* printf( "bufsz = %X\n", sc->sc_bufsz ); */ /* dma mem tag */ if (bus_dma_tag_create( NULL, /* parent */ 8, AMD_BUFSIZE_MAX*2, /* quadword alignment, boundary 8*1024*2 */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr, don't allocate above this */ BUS_SPACE_MAXADDR, /* highaddr, cant access above this */ NULL, NULL, /* filtfunc, filtfuncarg */ sc->sc_bufsz, /* maxsize */ 1, /* nsegments */ 0x30000, /* maxsegz, never more than 3*64k */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockfuncarg */ &sc->sc_prd_dtag) != 0) { device_printf(dev, "unable to create dma tag\n"); DELAY(2000); goto bad; } /* dma mem tag */ if (bus_dma_tag_create( NULL, /* parent */ 8, AMD_CONVERSIONSIZE_MAX, /* alignment, boundary 64k */ BUS_SPACE_MAXADDR_32BIT, /*lowaddr, don't allocate above this */ BUS_SPACE_MAXADDR, /* highaddr, cant access above this */ NULL, NULL, /* filtfunc, filtfuncarg */ AMD_CONVERSIONSIZE_MAX, /* maxsize 64k */ 1, /* nsegments */ 3*AMD_CONVERSIONSIZE_MAX, /* maxsegz, never more than 3*64k */ 0, /* flags */ NULL, /* lockfunc */ NULL, /* lockfuncarg */ &sc->sc_conversion_dtag) != 0) { device_printf(dev, "unable to create dma tag\n"); DELAY(2000); goto bad; } AMD_LOCK(sc); /* init */ i = amd_init(sc); AMD_UNLOCK(sc); if (i == -1) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* create/init mixer */ sc->sc_ac97_codec = AC97_CREATE(dev, sc, amd_codec); if (sc->sc_ac97_codec == NULL) { device_printf(dev, "ac97_create error\n"); goto bad; } if (mixer_init(dev, ac97_getmixerclass(), sc->sc_ac97_codec)) { device_printf(dev, "mixer_init error\n"); goto bad; } /* if (pcm_register(dev, sc, 2, 1 )) */ if (pcm_register(dev, sc, AMD_PCHANS, AMD_RCHANS)) { device_printf(dev, "pcm_register error\n"); goto bad; } if (pcm_addchan(dev, PCMDIR_PLAY, &amd_pchan_class, sc)) { device_printf(dev, "pcm_addchan (play) error addchan 0\n"); goto bad; } if (pcm_addchan(dev, PCMDIR_PLAY, &amd_pmono_chan_class, sc)) { device_printf(dev, "pcm_addchan (play) error addchan 1\n"); goto bad; } if (pcm_addchan(dev, PCMDIR_REC, &amd_rchan_class, sc)) { device_printf(dev, "pcm_addchan (rec) error addchan 2\n"); goto bad; } snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld %s", (sc->regtype == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(sc->reg), rman_get_start(sc->irq), PCM_KLDSTRING(snd_cs5536)); if (pcm_setstatus(dev, status)) { device_printf(dev, "attach: pcm_setstatus error\n"); goto bad; } mixer_hwvol_init(dev); /* * * Note: The above function call mutes the "rec" device. Must run * mixer rec 75 * on FreeBSD commandline before mic will record. * Or,put hint.pcm.0.rec="75" in /boot/device.hints . * Also, if we set it here directly, the FreeBSD mixer program * does not know, and thinks the rec level is still 0. */ /* * This is the code that somehow needs to be run to * set the Record Level so the mic will work. * However, finding how to get the interface is tricky * * #define SOUND_MIXER_RECLEV 11 * struct snd_mixer *m; * struct cdev *pdev; * * pdev = mixer_get_devt( dev ); * m = pdev->si_drv1; * * mixer_set( m, SOUND_MIXER_RECLEV, 75, 75 ); */ /* * We could just do it ourselves, but mixer wont know then. * So perhaps it is better to do it with the mixer program or hints, * if one could get hints to work. * * printf( " writing rec level " ); * amd_wrcd( NULL, sc, 0x1C, 0x204 ); * * printf( "Reg 1C = %X\n", amd_rdcd( NULL, sc, 0x1C ) ); * */ return 0; bad: if (sc->sc_ac97_codec) { ac97_destroy(sc->sc_ac97_codec); sc->sc_ac97_codec = 0; } if (sc->ih) bus_teardown_intr(dev, sc->irq, sc->ih); if (sc->irq) bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); if (sc->reg) bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); if (sc->sc_prd_dtag) bus_dma_tag_destroy(sc->sc_prd_dtag); if (sc->sc_conversion_dtag) bus_dma_tag_destroy(sc->sc_conversion_dtag); if (sc->sc_lock) snd_mtxfree(sc->sc_lock); free(sc, M_DEVBUF); return ENXIO; } static int amd_pci_detach(device_t dev) { struct sc_info *sc = pcm_getdevinfo(dev); int r; AMD_DEBUG(CALL, ("amd_pci_detach\n")); if ((r = pcm_unregister(dev)) != 0) { return r; } bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); bus_dmamap_unload( sc->sc_prd_dtag, sc->sc_prd_dmap ); bus_dmamem_free( sc->sc_prd_dtag, (void *)sc->sc_dtbl, sc->sc_prd_dmap ); bus_dma_tag_destroy(sc->sc_prd_dtag); bus_dmamap_unload( sc->sc_conversion_dtag, sc->sc_conversion_dmap ); bus_dmamem_free( sc->sc_conversion_dtag, (void *)sc->sc_conversionbuf, sc->sc_conversion_dmap ); bus_dma_tag_destroy(sc->sc_conversion_dtag); snd_mtxfree(sc->sc_lock); free(sc, M_DEVBUF); return 0; } static int amd_pci_suspend(device_t dev) { struct sc_info *sc; int i; /* printf( "\n\n *** AMD5536_SUSPEND\n\n" ); */ sc = pcm_getdevinfo( dev ); for (i=0; i<(AMD_PCHANS+AMD_RCHANS); i++) /* For all channels,*/ { sc->sc_ch[i].ch_was_running = sc->sc_ch[i].ch_run; /* Flag state.*/ if( sc->sc_ch[i].ch_run ) { amd_pchan_trigger( 0, &sc->sc_ch[i], PCMTRIG_ABORT ); /* Abort running DMA.*/ } } return 0; } static int amd_pci_resume(device_t dev) { struct sc_info *sc; struct sc_chinfo *ch; int i; /* printf( "\n\n *** AMD_PCI_RESUME\n\n" ); */ sc = pcm_getdevinfo( dev ); /* Reinit the codec (mixer) */ if( mixer_reinit( dev ) == -1 ) { device_printf( dev, "unable to reinit mixer.\n" ); return( ENXIO ); } /* Restart DMA that was in progress (aborted) at last suspend.*/ for (i=0; i<(AMD_PCHANS+AMD_RCHANS); i++) /* For all channels, */ { ch = &sc->sc_ch[i]; if( ch->ch_was_running ) /* If DMA was active,*/ { amd_pchan_setblocksize( 0, ch, ch->ch_blk_size ); /* Reset channel,*/ amd_pchan_setspeed( 0, ch, ch->ch_speed ); amd_pchan_trigger( 0, ch, PCMTRIG_START ); /* Restart DMA.*/ } } return 0; } static int amd_pci_shutdown(device_t dev) { AMD_DEBUG(CALL, ("amd_pci_shutdown\n")); return 0; } /* writing any value to AC97_RESET reg 0, resets the codec */ static void amd_codec_reset(struct sc_info *sc) { u_int32_t data32=0; int retry = 0; AMD_LOCK_ASSERT(sc); do { data32 = amd_rd_4( sc, ACC_CODEC_CNTL ); if( data32 & CMD_NEW ) printf( "Error. Codec not ready for command\n"); // lower this DELAY(1000); if ( !(data32 & CMD_NEW) ) { retry = 0; AMD_DEBUG(CALL, ("Codec wait !CMD_NEW successful %X\n", data32 )); } else { retry++; if (retry > 3) { device_printf(sc->dev, "Codec wait !CMD_NEW failed\n"); break; } device_printf(sc->dev, "Codec wait !CMD_NEW retry\n"); } } while (retry); if( retry > 3 ) return; /* Ready for a new command, so write a 0 to register 0; */ data32 = 0; /* register 0 */ data32 &= 0x7FFFFFFF; /* Clr bit 31 to write */ data32 |= CMD_NEW; amd_wr_4( sc, ACC_CODEC_CNTL, data32 ); retry = 0; do { DELAY(1000); data32 = amd_rd_4( sc, ACC_CODEC_CNTL ); if (!(data32 & CMD_NEW)) { retry = 0; /* printf("Codec reset successful\n", data32); */ } else { retry++; if (retry > 3) { device_printf(sc->dev, "Codec reset failed\n"); break; } device_printf(sc->dev, "Codec reset retry\n"); } } while (retry); } /* move monobuf to stereo buf Left Channel, and zero out Right Channel * Mono buf consists of word data. * So we move the mono words to the even words * of the stereo buf. * I assume that the Left Channel is the even words * and the Right Channel is the odd words * So the Left Channel Plays and the Right Channel is Silent * * Uses static global variables to pass arguments: * Assembler only accesses static global variables * numloops0 * srcbuf0: Left Channel Mono Play buffer * dstbuf0: Stereo Play buffer * */ static void movemono2stereo0() { __asm__( ".intel_syntax noprefix \n" " \n" "pushfd \n" "push EAX \n" "push ECX \n" "push ESI \n" "push EDI \n" " \n" "mov ECX, DWORD PTR [numloops0] \n" "mov ESI, DWORD PTR [srcbuf0] \n" "mov EDI, DWORD PTR [dstbuf0] \n" "cld \n" /* increment */ "mov EAX, 0 \n" " \n" "LUP0: \n" "lodsw [ESI] \n" /* load a word from mono buf */ "stosd [EDI] \n" /* store a dword to stereo buf */ "lodsw [ESI] \n" /* 2 */ "stosd [EDI] \n" "lodsw [ESI] \n" /* 3 */ "stosd [EDI] \n" "lodsw [ESI] \n" /* 4 */ "stosd [EDI] \n" "lodsw [ESI] \n" /* 5 */ "stosd [EDI] \n" "lodsw [ESI] \n" /* 6 */ "stosd [EDI] \n" "lodsw [ESI] \n" /* 7 */ "stosd [EDI] \n" "lodsw [ESI] \n" /* 8 */ "stosd [EDI] \n" " \n" "loop LUP0 \n" " \n" "pop EDI \n" "pop ESI \n" "pop ECX \n" "pop EAX \n" "popfd \n" " \n" ".att_syntax \n" ); } /* move monobuf to stereo buf Right Channel, and zero out Left Channel * Mono buf consists of word data. * So we move the mono words to the odd words * of the stereo buf. * I assume that the Right Channel is the odd words. * and the Left Channel is the even words. * So the Right Channel Plays and the Left Channel is Silent. * * Uses static global variables to pass arguments: * Assembler only accesses static global variables * numloops1 * srcbuf1: Right Channel Mono Play buffer * dstbuf1: Stereo Play buffer * */ static void movemono2stereo1() { __asm__( ".intel_syntax noprefix \n" " \n" "pushfd \n" "push EAX \n" "push ECX \n" "push ESI \n" "push EDI \n" " \n" "mov ECX, DWORD PTR [numloops1] \n" "mov ESI, DWORD PTR [srcbuf1] \n" "mov EDI, DWORD PTR [dstbuf1] \n" "cld \n" /* increment */ "mov EAX, 0 \n" " \n" "LUP1: \n" "lodsw [ESI] \n" /* load a word from mono buf */ "shl eax, 16 \n" /* shift into upper part of register */ "stosd [EDI] \n" /* store a dword to stereo buf */ "lodsw [ESI] \n" /* 2 */ "shl eax, 16 \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 3 */ "shl eax, 16 \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 4 */ "shl eax, 16 \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 5 */ "shl eax, 16 \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 6 */ "shl eax, 16 \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 7 */ "shl eax, 16 \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 8 */ "shl eax, 16 \n" "stosd [EDI] \n" " \n" "loop LUP1 \n" " \n" "pop EDI \n" "pop ESI \n" "pop ECX \n" "pop EAX \n" "popfd \n" " \n" ".att_syntax \n" ); } /* move Left & Right monobufs to stereo buf. * Mono bufs consists of word data. * So we move the Left mono words to the even stereo words * & we move the Right mono words to the odd stereo words * of the stereo buf. * I assume that the Left Channel is the even words. * and the Right Channel is the odd words. * So the Left Channel Plays and the Right Channel Plays simultaneously. * * Uses static global variables to pass arguments: * Assembler only accesses static global variables * numloops2 * srcbuf2: Left Channel Mono Play buffer * srRbuf2: Right Channel Mono Play buffer * dstbuf2: Stereo Play buffer * */ static void movemono2stereo2() { __asm__( ".intel_syntax noprefix \n" " \n" "pushfd \n" "push EAX \n" "push EBX \n" "push ECX \n" "push ESI \n" "push EDI \n" " \n" "mov ECX, DWORD PTR [numloops2] \n" "mov EBX, DWORD PTR [srcbuf2] \n" /* Left mono buffer */ "mov ESI, DWORD PTR [srRbuf2] \n" /* Right mono buffer */ "mov EDI, DWORD PTR [dstbuf2] \n" /* Stereo buffer */ " \n" "cld \n" /* increment */ "mov EAX, 0 \n" " \n" "LUP2: \n" "lodsw [ESI] \n" /* load a word from mono Right buf */ "shl eax, 16 \n" /* shift into upper part of register */ "mov ax, [ebx] \n" /* load a word from mono Left buf */ "inc ebx \n" /* unlike si, bx is not incremented */ "inc ebx \n" "stosd [EDI] \n" /* store a dword to stereo buf */ "lodsw [ESI] \n" /* 2 */ "shl eax, 16 \n" "mov ax, [ebx] \n" "inc ebx \n" "inc ebx \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 3 */ "shl eax, 16 \n" "mov ax, [ebx] \n" "inc ebx \n" "inc ebx \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 4 */ "shl eax, 16 \n" "mov ax, [ebx] \n" "inc ebx \n" "inc ebx \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 5 */ "shl eax, 16 \n" "mov ax, [ebx] \n" "inc ebx \n" "inc ebx \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 6 */ "shl eax, 16 \n" "mov ax, [ebx] \n" "inc ebx \n" "inc ebx \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 7 */ "shl eax, 16 \n" "mov ax, [ebx] \n" "inc ebx \n" "inc ebx \n" "stosd [EDI] \n" "lodsw [ESI] \n" /* 8 */ "shl eax, 16 \n" "mov ax, [ebx] \n" "inc ebx \n" "inc ebx \n" "stosd [EDI] \n" " \n" "loop LUP2 \n" " \n" "pop EDI \n" "pop ESI \n" "pop ECX \n" "pop EBX \n" "pop EAX \n" "popfd \n" " \n" ".att_syntax \n" ); } /* move sterobuf even words (Left Channel) to mono buf . * Stereo buf consists of double word data. * So we move the even words to the mono buf. * I assume that the Left Channel is the even words * and the Right Channel is the odd words * So the Left Channel Records and the Right is ignored * * Uses static global variables to pass arguments: * Assembler only accesses static global variables * numloops3 * srcbuf3: Stereo Record buffer * dstbuf3: Mono Record buffer * */ static void movestereo2mono3() { __asm__( ".intel_syntax noprefix \n" " \n" "pushfd \n" "push EAX \n" "push ECX \n" "push ESI \n" "push EDI \n" " \n" "mov ECX, DWORD PTR [numloops3] \n" "mov ESI, DWORD PTR [srcbuf3] \n" "mov EDI, DWORD PTR [dstbuf3] \n" "cld \n" /* increment */ "mov EAX, 0 \n" " \n" "LUP3: \n" "lodsd [ESI] \n" /* load a dword from stero buf */ "stosw [EDI] \n" /* store a word to mono buf */ "lodsd [ESI] \n" /* 2 */ "stosw [EDI] \n" "lodsd [ESI] \n" /* 3 */ "stosw [EDI] \n" "lodsd [ESI] \n" /* 4 */ "stosw [EDI] \n" "lodsd [ESI] \n" /* 5 */ "stosw [EDI] \n" "lodsd [ESI] \n" /* 6 */ "stosw [EDI] \n" "lodsd [ESI] \n" /* 7 */ "stosw [EDI] \n" "lodsd [ESI] \n" /* 8 */ "stosw [EDI] \n" " \n" "loop LUP3 \n" " \n" "pop EDI \n" "pop ESI \n" "pop ECX \n" "pop EAX \n" "popfd \n" " \n" ".att_syntax \n" ); } /* move monobuf to stereo buf Left Channel, and DO NOT zero out Right Channel * This routing is used only on a DMA TRIGGER * Mono buf consists of word data. * So we move the mono words to the even words * of the stereo buf. * I assume that the Left Channel is the even words * and the Right Channel is the odd words * So the Left Channel is filled and the Right Channel is left alone * * Uses static global variables to pass arguments: * Assembler only accesses static global variables * numloops4 * srcbuf4: Left Channel Mono Play buffer * dstbuf4: Stereo Play buffer * */ static void movemono2stereo4() { __asm__( ".intel_syntax noprefix \n" " \n" "pushfd \n" "push EAX \n" "push ECX \n" "push ESI \n" "push EDI \n" " \n" "mov ECX, DWORD PTR [numloops4] \n" "mov ESI, DWORD PTR [srcbuf4] \n" "mov EDI, DWORD PTR [dstbuf4] \n" "cld \n" /* increment */ "mov EAX, 0 \n" " \n" "LUP4: \n" "lodsw [ESI] \n" /* load a word from mono buf */ "stosw [EDI] \n" /* store a word to stereo buf */ "inc edi \n" /* skip right channel */ "inc edi \n" "lodsw [ESI] \n" /* 2 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 3 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 4 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 5 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 6 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 7 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 8 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" " \n" "loop LUP4 \n" " \n" "pop EDI \n" "pop ESI \n" "pop ECX \n" "pop EAX \n" "popfd \n" " \n" ".att_syntax \n" ); } /* move monobuf to stereo buf RIght Channel, and DO NOT zero out Left Channel * This routing is used only on a DMA TRIGGER * Mono buf consists of word data. * So we move the mono words to the even words * of the stereo buf. * I assume that the Left Channel is the even words * and the Right Channel is the odd words * So the Right Channel is filled and the Left Channel is left alone * * Uses static global variables to pass arguments: * Assembler only accesses static global variables * numloops5 * srcbuf5: Left Channel Mono Play buffer * dstbuf5: Stereo Play buffer * */ static void movemono2stereo5() { __asm__( ".intel_syntax noprefix \n" " \n" "pushfd \n" "push EAX \n" "push ECX \n" "push ESI \n" "push EDI \n" " \n" "mov ECX, DWORD PTR [numloops5] \n" "mov ESI, DWORD PTR [srcbuf5] \n" "mov EDI, DWORD PTR [dstbuf5] \n" "cld \n" /* increment */ "mov EAX, 0 \n" " \n" "LUP5: \n" "inc edi \n" /* skip left channel */ "inc edi \n" "lodsw [ESI] \n" /* load a word from mono buf */ "stosw [EDI] \n" /* store a word to stereo buf */ "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 2 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 3 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 4 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 5 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 6 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 7 */ "stosw [EDI] \n" "inc edi \n" "inc edi \n" "lodsw [ESI] \n" /* 8 */ "stosw [EDI] \n" " \n" "loop LUP5 \n" " \n" "pop EDI \n" "pop ESI \n" "pop ECX \n" "pop EAX \n" "popfd \n" " \n" ".att_syntax \n" ); } static device_method_t amd_methods[] = { DEVMETHOD(device_probe, amd_pci_probe), DEVMETHOD(device_attach, amd_pci_attach), DEVMETHOD(device_detach, amd_pci_detach), DEVMETHOD(device_suspend, amd_pci_suspend), DEVMETHOD(device_resume, amd_pci_resume), DEVMETHOD(device_shutdown, amd_pci_shutdown), { 0, 0 } }; static driver_t amd_driver = { "pcm", amd_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(snd_cs5536, pci, amd_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_cs5536, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_cs5536, 1);