diff options
author | tanimura <tanimura@FreeBSD.org> | 1999-11-22 06:07:49 +0000 |
---|---|---|
committer | tanimura <tanimura@FreeBSD.org> | 1999-11-22 06:07:49 +0000 |
commit | 05e9b4af3eeff6a4e22e35b13bbc16d56c3dce70 (patch) | |
tree | faaea41e349959e04b3e8cfe96726269290a60c8 /sys/dev/sound/pci/csapcm.c | |
parent | 913c1ce7346351f8ee703d9125f82353999c9d0a (diff) | |
download | FreeBSD-src-05e9b4af3eeff6a4e22e35b13bbc16d56c3dce70.zip FreeBSD-src-05e9b4af3eeff6a4e22e35b13bbc16d56c3dce70.tar.gz |
- Introduce the bridge drivers for Sound Blaser, GUS and Crystal
Semiconductor CS461x/428x.
- Add support for GUS and CS461x/428x pcm.
Bridges reviewed by: dfr, cg
GUS non-PnP support submitted by: Ville-Pertti Keinonen <will@iki.fi>
GUS PnP support tested by: Michiru Saito <mich@mtci.ne.jp>
Diffstat (limited to 'sys/dev/sound/pci/csapcm.c')
-rw-r--r-- | sys/dev/sound/pci/csapcm.c | 851 |
1 files changed, 851 insertions, 0 deletions
diff --git a/sys/dev/sound/pci/csapcm.c b/sys/dev/sound/pci/csapcm.c new file mode 100644 index 0000000..964011d --- /dev/null +++ b/sys/dev/sound/pci/csapcm.c @@ -0,0 +1,851 @@ +/*- + * Copyright (c) 1999 Seigo Tanimura + * 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. + * + * $FreeBSD$ + */ + +#include "opt_devfs.h" +#include "pci.h" +#include "csa.h" +#include "pcm.h" + +#include <sys/soundcard.h> +#include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/ac97.h> +#include <dev/sound/chip.h> +#include <dev/sound/pci/csareg.h> +#include <dev/sound/pci/csavar.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#if NCSA > 0 + +/* device private data */ +struct csa_info; + +struct csa_chinfo { + struct csa_info *parent; + pcm_channel *channel; + snd_dbuf *buffer; + int dir; + u_int32_t fmt; +}; + +struct csa_info { + csa_res res; /* resource */ + void *ih; /* Interrupt cookie */ + bus_dma_tag_t parent_dmat; /* DMA tag */ + + /* Contents of board's registers */ + u_long pfie; + u_long pctl; + u_long cctl; + struct csa_chinfo pch, rch; +}; + +/* -------------------------------------------------------------------- */ + +/* prototypes */ +static int csa_init(struct csa_info *); +static void csa_intr(void *); +static void csa_setplaysamplerate(csa_res *resp, u_long ulInRate); +static void csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate); +static void csa_startplaydma(struct csa_info *csa); +static void csa_startcapturedma(struct csa_info *csa); +static void csa_stopplaydma(struct csa_info *csa); +static void csa_stopcapturedma(struct csa_info *csa); +static void csa_powerupadc(csa_res *resp); +static void csa_powerupdac(csa_res *resp); +static int csa_startdsp(csa_res *resp); +static int csa_allocres(struct csa_info *scp, device_t dev); +static void csa_releaseres(struct csa_info *scp, device_t dev); + +/* talk to the codec - called from ac97.c */ +static u_int32_t csa_rdcd(void *, int); +static void csa_wrcd(void *, int, u_int32_t); + +/* channel interface */ +static void *csachan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir); +static int csachan_setdir(void *data, int dir); +static int csachan_setformat(void *data, u_int32_t format); +static int csachan_setspeed(void *data, u_int32_t speed); +static int csachan_setblocksize(void *data, u_int32_t blocksize); +static int csachan_trigger(void *data, int go); +static int csachan_getptr(void *data); +static pcmchan_caps *csachan_getcaps(void *data); + +static pcmchan_caps csa_playcaps = { + 8000, 48000, + AFMT_STEREO | AFMT_U8 | AFMT_S8 | AFMT_S16_LE | AFMT_S16_BE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcmchan_caps csa_reccaps = { + 11025, 48000, + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE +}; + +static pcm_channel csa_chantemplate = { + csachan_init, + csachan_setdir, + csachan_setformat, + csachan_setspeed, + csachan_setblocksize, + csachan_trigger, + csachan_getptr, + csachan_getcaps, +}; + +/* -------------------------------------------------------------------- */ + +/* channel interface */ +static void * +csachan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + struct csa_info *csa = devinfo; + struct csa_chinfo *ch = (dir == PCMDIR_PLAY)? &csa->pch : &csa->rch; + + ch->parent = csa; + ch->channel = c; + ch->buffer = b; + ch->buffer->bufsize = CS461x_BUFFSIZE; + if (chn_allocbuf(ch->buffer, csa->parent_dmat) == -1) return NULL; + return ch; +} + +static int +csachan_setdir(void *data, int dir) +{ + struct csa_chinfo *ch = data; + struct csa_info *csa = ch->parent; + csa_res *resp; + + resp = &csa->res; + + if (dir == PCMDIR_PLAY) + csa_writemem(resp, BA1_PBA, vtophys(ch->buffer->buf)); + else + csa_writemem(resp, BA1_CBA, vtophys(ch->buffer->buf)); + ch->dir = dir; + return 0; +} + +static int +csachan_setformat(void *data, u_int32_t format) +{ + struct csa_chinfo *ch = data; + struct csa_info *csa = ch->parent; + u_long pdtc; + csa_res *resp; + + resp = &csa->res; + + if (ch->dir == PCMDIR_REC) + csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); + else { + csa->pfie = csa_readmem(resp, BA1_PFIE) & ~0x0000f03f; + if (format & AFMT_U8 || format & AFMT_U16_LE || format & AFMT_U16_BE) + csa->pfie |= 0x8000; + if (format & AFMT_S16_BE || format & AFMT_U16_BE) + csa->pfie |= 0x4000; + if (!(format & AFMT_STEREO)) + csa->pfie |= 0x2000; + if (format & AFMT_U8 || format & AFMT_S8) + csa->pfie |= 0x1000; + csa_writemem(resp, BA1_PFIE, csa->pfie); + pdtc = csa_readmem(resp, BA1_PDTC) & ~0x000003ff; + if ((format & AFMT_S16_BE || format & AFMT_U16_BE || format & AFMT_S16_LE || format & AFMT_U16_LE) && (format & AFMT_STEREO)) + pdtc |= 0x00f; + else if ((format & AFMT_S16_BE || format & AFMT_U16_BE || format & AFMT_S16_LE || format & AFMT_U16_LE) || (format & AFMT_STEREO)) + pdtc |= 0x007; + else + pdtc |= 0x003; + csa_writemem(resp, BA1_PDTC, pdtc); + } + ch->fmt = format; + return 0; +} + +static int +csachan_setspeed(void *data, u_int32_t speed) +{ + struct csa_chinfo *ch = data; + struct csa_info *csa = ch->parent; + csa_res *resp; + + resp = &csa->res; + + if (ch->dir == PCMDIR_PLAY) + csa_setplaysamplerate(resp, speed); + else if (ch->dir == PCMDIR_REC) + csa_setcapturesamplerate(resp, speed); + + /* rec/play speeds locked together - should indicate in flags */ +#if 0 + if (ch->direction == PCMDIR_PLAY) d->rec[0].speed = speed; + else d->play[0].speed = speed; +#endif + return speed; /* XXX calc real speed */ +} + +static void +csa_setplaysamplerate(csa_res *resp, u_long ulInRate) +{ + u_long ulTemp1, ulTemp2; + u_long ulPhiIncr; + u_long ulCorrectionPerGOF, ulCorrectionPerSec; + u_long ulOutRate; + + ulOutRate = 48000; + + /* + * Compute the values used to drive the actual sample rate conversion. + * The following formulas are being computed, using inline assembly + * since we need to use 64 bit arithmetic to compute the values: + * + * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) + * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / + * GOF_PER_SEC) + * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - + * GOF_PER_SEC * ulCorrectionPerGOF + * + * i.e. + * + * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) + * ulCorrectionPerGOF:ulCorrectionPerSec = + * dividend:remainder(ulOther / GOF_PER_SEC) + */ + ulTemp1 = ulInRate << 16; + ulPhiIncr = ulTemp1 / ulOutRate; + ulTemp1 -= ulPhiIncr * ulOutRate; + ulTemp1 <<= 10; + ulPhiIncr <<= 10; + ulTemp2 = ulTemp1 / ulOutRate; + ulPhiIncr += ulTemp2; + ulTemp1 -= ulTemp2 * ulOutRate; + ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; + ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; + ulCorrectionPerSec = ulTemp1; + + /* + * Fill in the SampleRateConverter control block. + */ + csa_writemem(resp, BA1_PSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); + csa_writemem(resp, BA1_PPI, ulPhiIncr); +} + +static void +csa_setcapturesamplerate(csa_res *resp, u_long ulOutRate) +{ + u_long ulPhiIncr, ulCoeffIncr, ulTemp1, ulTemp2; + u_long ulCorrectionPerGOF, ulCorrectionPerSec, ulInitialDelay; + u_long dwFrameGroupLength, dwCnt; + u_long ulInRate; + + ulInRate = 48000; + + /* + * We can only decimate by up to a factor of 1/9th the hardware rate. + * Return an error if an attempt is made to stray outside that limit. + */ + if((ulOutRate * 9) < ulInRate) + return; + + /* + * We can not capture at at rate greater than the Input Rate (48000). + * Return an error if an attempt is made to stray outside that limit. + */ + if(ulOutRate > ulInRate) + return; + + /* + * Compute the values used to drive the actual sample rate conversion. + * The following formulas are being computed, using inline assembly + * since we need to use 64 bit arithmetic to compute the values: + * + * ulCoeffIncr = -floor((Fs,out * 2^23) / Fs,in) + * ulPhiIncr = floor((Fs,in * 2^26) / Fs,out) + * ulCorrectionPerGOF = floor((Fs,in * 2^26 - Fs,out * ulPhiIncr) / + * GOF_PER_SEC) + * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - + * GOF_PER_SEC * ulCorrectionPerGOF + * ulInitialDelay = ceil((24 * Fs,in) / Fs,out) + * + * i.e. + * + * ulCoeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in)) + * ulPhiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) + * ulCorrectionPerGOF:ulCorrectionPerSec = + * dividend:remainder(ulOther / GOF_PER_SEC) + * ulInitialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out) + */ + ulTemp1 = ulOutRate << 16; + ulCoeffIncr = ulTemp1 / ulInRate; + ulTemp1 -= ulCoeffIncr * ulInRate; + ulTemp1 <<= 7; + ulCoeffIncr <<= 7; + ulCoeffIncr += ulTemp1 / ulInRate; + ulCoeffIncr ^= 0xFFFFFFFF; + ulCoeffIncr++; + ulTemp1 = ulInRate << 16; + ulPhiIncr = ulTemp1 / ulOutRate; + ulTemp1 -= ulPhiIncr * ulOutRate; + ulTemp1 <<= 10; + ulPhiIncr <<= 10; + ulTemp2 = ulTemp1 / ulOutRate; + ulPhiIncr += ulTemp2; + ulTemp1 -= ulTemp2 * ulOutRate; + ulCorrectionPerGOF = ulTemp1 / GOF_PER_SEC; + ulTemp1 -= ulCorrectionPerGOF * GOF_PER_SEC; + ulCorrectionPerSec = ulTemp1; + ulInitialDelay = ((ulInRate * 24) + ulOutRate - 1) / ulOutRate; + + /* + * Fill in the VariDecimate control block. + */ + csa_writemem(resp, BA1_CSRC, + ((ulCorrectionPerSec << 16) & 0xFFFF0000) | (ulCorrectionPerGOF & 0xFFFF)); + csa_writemem(resp, BA1_CCI, ulCoeffIncr); + csa_writemem(resp, BA1_CD, + (((BA1_VARIDEC_BUF_1 + (ulInitialDelay << 2)) << 16) & 0xFFFF0000) | 0x80); + csa_writemem(resp, BA1_CPI, ulPhiIncr); + + /* + * Figure out the frame group length for the write back task. Basically, + * this is just the factors of 24000 (2^6*3*5^3) that are not present in + * the output sample rate. + */ + dwFrameGroupLength = 1; + for(dwCnt = 2; dwCnt <= 64; dwCnt *= 2) + { + if(((ulOutRate / dwCnt) * dwCnt) != + ulOutRate) + { + dwFrameGroupLength *= 2; + } + } + if(((ulOutRate / 3) * 3) != + ulOutRate) + { + dwFrameGroupLength *= 3; + } + for(dwCnt = 5; dwCnt <= 125; dwCnt *= 5) + { + if(((ulOutRate / dwCnt) * dwCnt) != + ulOutRate) + { + dwFrameGroupLength *= 5; + } + } + + /* + * Fill in the WriteBack control block. + */ + csa_writemem(resp, BA1_CFG1, dwFrameGroupLength); + csa_writemem(resp, BA1_CFG2, (0x00800000 | dwFrameGroupLength)); + csa_writemem(resp, BA1_CCST, 0x0000FFFF); + csa_writemem(resp, BA1_CSPB, ((65536 * ulOutRate) / 24000)); + csa_writemem(resp, (BA1_CSPB + 4), 0x0000FFFF); +} + +static int +csachan_setblocksize(void *data, u_int32_t blocksize) +{ +#if notdef + return blocksize; +#else + struct csa_chinfo *ch = data; + return ch->buffer->bufsize / 2; +#endif /* notdef */ +} + +static int +csachan_trigger(void *data, int go) +{ + struct csa_chinfo *ch = data; + struct csa_info *csa = ch->parent; + + if (ch->dir == PCMDIR_PLAY) { + if (go == PCMTRIG_START) + csa_startplaydma(csa); + else + csa_stopplaydma(csa); + } else { + if (go == PCMTRIG_START) + csa_startcapturedma(csa); + else + csa_stopcapturedma(csa); + } + return 0; +} + +static void +csa_startplaydma(struct csa_info *csa) +{ + csa_res *resp; + u_long ul; + + resp = &csa->res; + ul = csa_readmem(resp, BA1_PCTL); + ul &= 0x0000ffff; + csa_writemem(resp, BA1_PCTL, ul | csa->pctl); + csa_writemem(resp, BA1_PVOL, 0x80008000); +} + +static void +csa_startcapturedma(struct csa_info *csa) +{ + csa_res *resp; + u_long ul; + + resp = &csa->res; + ul = csa_readmem(resp, BA1_CCTL); + ul &= 0xffff0000; + csa_writemem(resp, BA1_CCTL, ul | csa->cctl); + csa_writemem(resp, BA1_CVOL, 0x80008000); +} + +static void +csa_stopplaydma(struct csa_info *csa) +{ + csa_res *resp; + u_long ul; + + resp = &csa->res; + ul = csa_readmem(resp, BA1_PCTL); + csa->pctl = ul & 0xffff0000; + csa_writemem(resp, BA1_PCTL, ul & 0x0000ffff); + csa_writemem(resp, BA1_PVOL, 0xffffffff); +} + +static void +csa_stopcapturedma(struct csa_info *csa) +{ + csa_res *resp; + u_long ul; + + resp = &csa->res; + ul = csa_readmem(resp, BA1_CCTL); + csa->cctl = ul & 0x0000ffff; + csa_writemem(resp, BA1_CCTL, ul & 0xffff0000); + csa_writemem(resp, BA1_CVOL, 0xffffffff); +} + +static void +csa_powerupdac(csa_res *resp) +{ + int i; + u_long ul; + + /* + * Power on the DACs on the AC97 codec. We turn off the DAC + * powerdown bit and write the new value of the power control + * register. + */ + ul = csa_readio(resp, BA0_AC97_POWERDOWN); + ul &= 0xfdff; + csa_writeio(resp, BA0_AC97_POWERDOWN, ul); + + /* + * Now, we wait until we sample a DAC ready state. + */ + for (i = 0 ; i < 32 ; i++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + DELAY(125); + + /* + * Read the current state of the power control register. + */ + ul = csa_readio(resp, BA0_AC97_POWERDOWN); + + /* + * If the DAC ready state bit is set, then stop waiting. + */ + if ((ul & 0x2) != 0) + break; + } + /* + * The DACs are now calibrated, so we can unmute the DAC output. + */ + csa_writeio(resp, BA0_AC97_PCM_OUT_VOLUME, 0x0808); +} + +static void +csa_powerupadc(csa_res *resp) +{ + int i; + u_long ul; + + /* + * Power on the ADCs on the AC97 codec. We turn off the ADC + * powerdown bit and write the new value of the power control + * register. + */ + ul = csa_readio(resp, BA0_AC97_POWERDOWN); + ul &= 0xfeff; + csa_writeio(resp, BA0_AC97_POWERDOWN, ul); + + /* + * Now, we wait until we sample a ADC ready state. + */ + for (i = 0 ; i < 32 ; i++) { + /* + * First, lets wait a short while to let things settle out a + * bit, and to prevent retrying the read too quickly. + */ + DELAY(125); + + /* + * Read the current state of the power control register. + */ + ul = csa_readio(resp, BA0_AC97_POWERDOWN); + + /* + * If the ADC ready state bit is set, then stop waiting. + */ + if ((ul & 0x1) != 0) + break; + } +} + +static int +csa_startdsp(csa_res *resp) +{ + int i; + u_long ul; + + /* + * Set the frame timer to reflect the number of cycles per frame. + */ + csa_writemem(resp, BA1_FRMT, 0xadf); + + /* + * Turn on the run, run at frame, and DMA enable bits in the local copy of + * the SP control register. + */ + csa_writemem(resp, BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN); + + /* + * Wait until the run at frame bit resets itself in the SP control + * register. + */ + ul = 0; + for (i = 0 ; i < 25 ; i++) { + /* + * Wait a little bit, so we don't issue PCI reads too frequently. + */ +#if notdef + DELAY(1000); +#else + DELAY(125); +#endif /* notdef */ + /* + * Fetch the current value of the SP status register. + */ + ul = csa_readmem(resp, BA1_SPCR); + + /* + * If the run at frame bit has reset, then stop waiting. + */ + if((ul & SPCR_RUNFR) == 0) + break; + } + /* + * If the run at frame bit never reset, then return an error. + */ + if((ul & SPCR_RUNFR) != 0) + return (EAGAIN); + + return (0); +} + +static int +csachan_getptr(void *data) +{ + struct csa_chinfo *ch = data; + struct csa_info *csa = ch->parent; + csa_res *resp; + int ptr; + + resp = &csa->res; + + if (ch->dir == PCMDIR_PLAY) { + ptr = csa_readmem(resp, BA1_PBA) - vtophys(ch->buffer->buf); + if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) + ptr >>= 1; + } else { + ptr = csa_readmem(resp, BA1_CBA) - vtophys(ch->buffer->buf); + if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0) + ptr >>= 1; + } + + return (ptr); +} + +static pcmchan_caps * +csachan_getcaps(void *data) +{ + struct csa_chinfo *ch = data; + return (ch->dir == PCMDIR_PLAY)? &csa_playcaps : &csa_reccaps; +} + +/* The interrupt handler */ +static void +csa_intr (void *p) +{ + struct csa_info *csa = p; + csa_res *resp; + u_int hisr; + + resp = &csa->res; + + /* Is this interrupt for us? */ + /* XXX The parent device should handle this. */ + hisr = csa_readio(resp, BA0_HISR); + if ((hisr & ~HISR_INTENA) == 0) { + /* Throw an eoi. */ + csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); + return; + } + + if ((hisr & HISR_VC0) != 0) + chn_intr(csa->pch.channel); + if ((hisr & HISR_VC1) != 0) + chn_intr(csa->rch.channel); + + /* Throw an eoi. */ + csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); +} + +/* -------------------------------------------------------------------- */ + +/* + * Probe and attach the card + */ + +static int +csa_init(struct csa_info *csa) +{ + csa_res *resp; + + resp = &csa->res; + + csa->pfie = 0; + csa_stopplaydma(csa); + csa_stopcapturedma(csa); + + /* Crank up the power on the DAC and ADC. */ + csa_powerupadc(resp); + csa_powerupdac(resp); + + csa_setplaysamplerate(resp, 8000); + csa_setcapturesamplerate(resp, 8000); + + if (csa_startdsp(resp)) + return (1); + + return 0; +} + +/* Allocates resources. */ +static int +csa_allocres(struct csa_info *csa, device_t dev) +{ + csa_res *resp; + + resp = &csa->res; + if (resp->io == NULL) { + resp->io = bus_alloc_resource(dev, SYS_RES_MEMORY, &resp->io_rid, 0, ~0, CS461x_IO_SIZE, RF_ACTIVE); + if (resp->io == NULL) + return (1); + } + if (resp->mem == NULL) { + resp->mem = bus_alloc_resource(dev, SYS_RES_MEMORY, &resp->mem_rid, 0, ~0, CS461x_MEM_SIZE, RF_ACTIVE); + if (resp->mem == NULL) + return (1); + } + if (resp->irq == NULL) { + resp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &resp->irq_rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (resp->irq == NULL) + return (1); + } + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/CS461x_BUFFSIZE, /*boundary*/CS461x_BUFFSIZE, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/CS461x_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &csa->parent_dmat) != 0) + return (1); + + return (0); +} + +/* Releases resources. */ +static void +csa_releaseres(struct csa_info *csa, device_t dev) +{ + csa_res *resp; + + resp = &csa->res; + if (resp->irq != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, resp->irq_rid, resp->irq); + resp->irq = NULL; + } + if (resp->io != NULL) { + bus_release_resource(dev, SYS_RES_MEMORY, resp->io_rid, resp->io); + resp->io = NULL; + } + if (resp->mem != NULL) { + bus_release_resource(dev, SYS_RES_MEMORY, resp->mem_rid, resp->mem); + resp->mem = NULL; + } +} + +static int pcmcsa_probe(device_t dev); +static int pcmcsa_attach(device_t dev); + +static int +pcmcsa_probe(device_t dev) +{ + char *s; + struct sndcard_func *func; + + /* The parent device has already been probed. */ + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_PCM) + return (ENXIO); + + s = "CS461x PCM Audio"; + + device_set_desc(dev, s); + return (0); +} + +static int +pcmcsa_attach(device_t dev) +{ + snddev_info *devinfo; + struct csa_info *csa; + csa_res *resp; + int unit; + char status[SND_STATUSLEN]; + struct ac97_info *codec; + + devinfo = device_get_softc(dev); + csa = malloc(sizeof(*csa), M_DEVBUF, M_NOWAIT); + if (csa == NULL) + return (ENOMEM); + bzero(csa, sizeof(*csa)); + unit = device_get_unit(dev); + + /* Allocate the resources. */ + resp = &csa->res; + resp->io_rid = CS461x_IO_OFFSET; + resp->mem_rid = CS461x_MEM_OFFSET; + resp->irq_rid = 0; + if (csa_allocres(csa, dev)) { + csa_releaseres(csa, dev); + return (ENXIO); + } + + if (csa_init(csa)) { + csa_releaseres(csa, dev); + return (ENXIO); + } + codec = ac97_create(csa, csa_rdcd, csa_wrcd); + if (codec == NULL) + return (ENXIO); + mixer_init(devinfo, &ac97_mixer, codec); + + snprintf(status, SND_STATUSLEN, "at irq %ld", rman_get_start(resp->irq)); + + /* Enable interrupt. */ + if (bus_setup_intr(dev, resp->irq, INTR_TYPE_TTY, csa_intr, csa, &csa->ih)) { + csa_releaseres(csa, dev); + return (ENXIO); + } + if ((csa_readio(resp, BA0_HISR) & HISR_INTENA) == 0) + csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); + csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f); + csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001); + + if (pcm_register(dev, csa, 1, 1)) { + csa_releaseres(csa, dev); + return (ENXIO); + } + pcm_addchan(dev, PCMDIR_REC, &csa_chantemplate, csa); + pcm_addchan(dev, PCMDIR_PLAY, &csa_chantemplate, csa); + pcm_setstatus(dev, status); + + return (0); +} + +/* ac97 codec */ + +static u_int32_t +csa_rdcd(void *devinfo, int regno) +{ + u_int32_t data; + struct csa_info *csa = (struct csa_info *)devinfo; + + if (csa_readcodec(&csa->res, regno + BA0_AC97_RESET, &data)) + data = 0; + + return data; +} + +static void +csa_wrcd(void *devinfo, int regno, u_int32_t data) +{ + struct csa_info *csa = (struct csa_info *)devinfo; + + csa_writecodec(&csa->res, regno + BA0_AC97_RESET, data); +} + +static device_method_t pcmcsa_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , pcmcsa_probe ), + DEVMETHOD(device_attach, pcmcsa_attach), + + { 0, 0 }, +}; + +static driver_t pcmcsa_driver = { + "pcm", + pcmcsa_methods, + sizeof(snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(pcmcsa, csa, pcmcsa_driver, pcm_devclass, 0, 0); + +#endif /* NCSA > 0 */ |