freebsd-skq/sys/dev/sound/pci/csapcm.c
Brian Feldman 8fb9a995cf The newpcm headers currently #define away INTR_MPSAFE and INTR_TYPE_AV
because they bogusly check for defined(INTR_MPSAFE) -- something which
never was a #define.  Correct the definitions.

This make INTR_TYPE_AV finally get used instead of the lower-priority
INTR_TYPE_TTY, so it's quite possible some improvement will be had
on sound driver performance.  It would also make all the drivers
marked INTR_MPSAFE actually run without Giant (which does seem to
work for me), but:
	INTR_MPSAFE HAS BEEN REMOVED FROM EVERY SOUND DRIVER!
It needs to be re-added on a case-by-case basis since there is no one
who will vouch for which sound drivers, if any, willy actually operate
correctly without Giant, since there hasn't been testing because of
this bug disabling INTR_MPSAFE.

Found by:	"Yuriy Tsibizov" <Yuriy.Tsibizov@gfk.ru>
2004-04-14 14:57:49 +00:00

854 lines
21 KiB
C

/*
* Copyright (c) 1999 Seigo Tanimura
* All rights reserved.
*
* Portions of this source are based on cwcealdr.cpp and dhwiface.cpp in
* cwcealdr1.zip, the sample sources by Crystal Semiconductor.
* Copyright (c) 1996-1998 Crystal Semiconductor Corp.
*
* 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.
*/
#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 <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
SND_DECLARE_FILE("$FreeBSD$");
/* Buffer size on dma transfer. Fixed for CS416x. */
#define CS461x_BUFFSIZE (4 * 1024)
#define GOF_PER_SEC 200
/* device private data */
struct csa_info;
struct csa_chinfo {
struct csa_info *parent;
struct pcm_channel *channel;
struct snd_dbuf *buffer;
int dir;
u_int32_t fmt, spd;
int dma;
};
struct csa_info {
csa_res res; /* resource */
void *ih; /* Interrupt cookie */
bus_dma_tag_t parent_dmat; /* DMA tag */
struct csa_bridgeinfo *binfo; /* The state of the parent. */
struct csa_card *card;
int active;
/* 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 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);
static u_int32_t csa_playfmt[] = {
AFMT_U8,
AFMT_STEREO | AFMT_U8,
AFMT_S8,
AFMT_STEREO | AFMT_S8,
AFMT_S16_LE,
AFMT_STEREO | AFMT_S16_LE,
AFMT_S16_BE,
AFMT_STEREO | AFMT_S16_BE,
0
};
static struct pcmchan_caps csa_playcaps = {8000, 48000, csa_playfmt, 0};
static u_int32_t csa_recfmt[] = {
AFMT_S16_LE,
AFMT_STEREO | AFMT_S16_LE,
0
};
static struct pcmchan_caps csa_reccaps = {11025, 48000, csa_recfmt, 0};
/* -------------------------------------------------------------------- */
static int
csa_active(struct csa_info *csa, int run)
{
int old, go;
old = csa->active;
csa->active += run;
if ((csa->active == 0 && old == 1) || (csa->active == 1 && old == 0)) {
go = csa->active;
if (csa->card->active)
return csa->card->active(go);
}
return 0;
}
/* -------------------------------------------------------------------- */
/* ac97 codec */
static int
csa_rdcd(kobj_t obj, void *devinfo, int regno)
{
u_int32_t data;
struct csa_info *csa = (struct csa_info *)devinfo;
csa_active(csa, 1);
if (csa_readcodec(&csa->res, regno + BA0_AC97_RESET, &data))
data = 0;
csa_active(csa, -1);
return data;
}
static int
csa_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data)
{
struct csa_info *csa = (struct csa_info *)devinfo;
csa_active(csa, 1);
csa_writecodec(&csa->res, regno + BA0_AC97_RESET, data);
csa_active(csa, -1);
return 0;
}
static kobj_method_t csa_ac97_methods[] = {
KOBJMETHOD(ac97_read, csa_rdcd),
KOBJMETHOD(ac97_write, csa_wrcd),
{ 0, 0 }
};
AC97_DECLARE(csa_ac97);
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 void
csa_startplaydma(struct csa_info *csa)
{
csa_res *resp;
u_long ul;
if (!csa->pch.dma) {
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);
csa->pch.dma = 1;
}
}
static void
csa_startcapturedma(struct csa_info *csa)
{
csa_res *resp;
u_long ul;
if (!csa->rch.dma) {
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);
csa->rch.dma = 1;
}
}
static void
csa_stopplaydma(struct csa_info *csa)
{
csa_res *resp;
u_long ul;
if (csa->pch.dma) {
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);
csa->pch.dma = 0;
/*
* The bitwise pointer of the serial FIFO in the DSP
* seems to make an error upon starting or stopping the
* DSP. Clear the FIFO and correct the pointer if we
* are not capturing.
*/
if (!csa->rch.dma) {
csa_clearserialfifos(resp);
csa_writeio(resp, BA0_SERBSP, 0);
}
}
}
static void
csa_stopcapturedma(struct csa_info *csa)
{
csa_res *resp;
u_long ul;
if (csa->rch.dma) {
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);
csa->rch.dma = 0;
/*
* The bitwise pointer of the serial FIFO in the DSP
* seems to make an error upon starting or stopping the
* DSP. Clear the FIFO and correct the pointer if we
* are not playing.
*/
if (!csa->pch.dma) {
csa_clearserialfifos(resp);
csa_writeio(resp, BA0_SERBSP, 0);
}
}
}
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.
*/
DELAY(50);
/*
* 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
csa_setupchan(struct csa_chinfo *ch)
{
struct csa_info *csa = ch->parent;
csa_res *resp = &csa->res;
u_long pdtc, tmp;
if (ch->dir == PCMDIR_PLAY) {
/* direction */
csa_writemem(resp, BA1_PBA, sndbuf_getbufaddr(ch->buffer));
/* format */
csa->pfie = csa_readmem(resp, BA1_PFIE) & ~0x0000f03f;
if (!(ch->fmt & AFMT_SIGNED))
csa->pfie |= 0x8000;
if (ch->fmt & AFMT_BIGENDIAN)
csa->pfie |= 0x4000;
if (!(ch->fmt & AFMT_STEREO))
csa->pfie |= 0x2000;
if (ch->fmt & AFMT_8BIT)
csa->pfie |= 0x1000;
csa_writemem(resp, BA1_PFIE, csa->pfie);
tmp = 4;
if (ch->fmt & AFMT_16BIT)
tmp <<= 1;
if (ch->fmt & AFMT_STEREO)
tmp <<= 1;
tmp--;
pdtc = csa_readmem(resp, BA1_PDTC) & ~0x000001ff;
pdtc |= tmp;
csa_writemem(resp, BA1_PDTC, pdtc);
/* rate */
csa_setplaysamplerate(resp, ch->spd);
} else if (ch->dir == PCMDIR_REC) {
/* direction */
csa_writemem(resp, BA1_CBA, sndbuf_getbufaddr(ch->buffer));
/* format */
csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001);
/* rate */
csa_setcapturesamplerate(resp, ch->spd);
}
return 0;
}
/* -------------------------------------------------------------------- */
/* channel interface */
static void *
csachan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct 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->dir = dir;
if (sndbuf_alloc(ch->buffer, csa->parent_dmat, CS461x_BUFFSIZE) == -1) return NULL;
return ch;
}
static int
csachan_setformat(kobj_t obj, void *data, u_int32_t format)
{
struct csa_chinfo *ch = data;
ch->fmt = format;
return 0;
}
static int
csachan_setspeed(kobj_t obj, void *data, u_int32_t speed)
{
struct csa_chinfo *ch = data;
ch->spd = speed;
return ch->spd; /* XXX calc real speed */
}
static int
csachan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
{
return CS461x_BUFFSIZE / 2;
}
static int
csachan_trigger(kobj_t obj, void *data, int go)
{
struct csa_chinfo *ch = data;
struct csa_info *csa = ch->parent;
if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)
return 0;
if (go == PCMTRIG_START) {
csa_active(csa, 1);
csa_setupchan(ch);
if (ch->dir == PCMDIR_PLAY)
csa_startplaydma(csa);
else
csa_startcapturedma(csa);
} else {
if (ch->dir == PCMDIR_PLAY)
csa_stopplaydma(csa);
else
csa_stopcapturedma(csa);
csa_active(csa, -1);
}
return 0;
}
static int
csachan_getptr(kobj_t obj, 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) - sndbuf_getbufaddr(ch->buffer);
if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0)
ptr >>= 1;
} else {
ptr = csa_readmem(resp, BA1_CBA) - sndbuf_getbufaddr(ch->buffer);
if ((ch->fmt & AFMT_U8) != 0 || (ch->fmt & AFMT_S8) != 0)
ptr >>= 1;
}
return (ptr);
}
static struct pcmchan_caps *
csachan_getcaps(kobj_t obj, void *data)
{
struct csa_chinfo *ch = data;
return (ch->dir == PCMDIR_PLAY)? &csa_playcaps : &csa_reccaps;
}
static kobj_method_t csachan_methods[] = {
KOBJMETHOD(channel_init, csachan_init),
KOBJMETHOD(channel_setformat, csachan_setformat),
KOBJMETHOD(channel_setspeed, csachan_setspeed),
KOBJMETHOD(channel_setblocksize, csachan_setblocksize),
KOBJMETHOD(channel_trigger, csachan_trigger),
KOBJMETHOD(channel_getptr, csachan_getptr),
KOBJMETHOD(channel_getcaps, csachan_getcaps),
{ 0, 0 }
};
CHANNEL_DECLARE(csachan);
/* -------------------------------------------------------------------- */
/* The interrupt handler */
static void
csa_intr(void *p)
{
struct csa_info *csa = p;
if ((csa->binfo->hisr & HISR_VC0) != 0)
chn_intr(csa->pch.channel);
if ((csa->binfo->hisr & HISR_VC1) != 0)
chn_intr(csa->rch.channel);
}
/* -------------------------------------------------------------------- */
/*
* 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);
if (csa_startdsp(resp))
return (1);
/* Crank up the power on the DAC and ADC. */
csa_setplaysamplerate(resp, 8000);
csa_setcapturesamplerate(resp, 8000);
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_any(dev, SYS_RES_MEMORY,
&resp->io_rid, RF_ACTIVE);
if (resp->io == NULL)
return (1);
}
if (resp->mem == NULL) {
resp->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&resp->mem_rid, RF_ACTIVE);
if (resp->mem == NULL)
return (1);
}
if (resp->irq == NULL) {
resp->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&resp->irq_rid, 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, /*lockfunc*/busdma_lock_mutex,
/*lockarg*/&Giant, &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) {
if (csa->ih)
bus_teardown_intr(dev, resp->irq, csa->ih);
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;
}
if (csa->parent_dmat != NULL) {
bus_dma_tag_destroy(csa->parent_dmat);
csa->parent_dmat = NULL;
}
if (csa != NULL) {
free(csa, M_DEVBUF);
csa = NULL;
}
}
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)
{
struct csa_info *csa;
csa_res *resp;
int unit;
char status[SND_STATUSLEN];
struct ac97_info *codec;
struct sndcard_func *func;
csa = malloc(sizeof(*csa), M_DEVBUF, M_NOWAIT | M_ZERO);
if (csa == NULL)
return (ENOMEM);
unit = device_get_unit(dev);
func = device_get_ivars(dev);
csa->binfo = func->varinfo;
/*
* Fake the status of DMA so that the initial value of
* PCTL and CCTL can be stored into csa->pctl and csa->cctl,
* respectively.
*/
csa->pch.dma = csa->rch.dma = 1;
csa->active = 0;
csa->card = csa->binfo->card;
/* Allocate the resources. */
resp = &csa->res;
resp->io_rid = PCIR_BAR(0);
resp->mem_rid = PCIR_BAR(1);
resp->irq_rid = 0;
if (csa_allocres(csa, dev)) {
csa_releaseres(csa, dev);
return (ENXIO);
}
csa_active(csa, 1);
if (csa_init(csa)) {
csa_releaseres(csa, dev);
return (ENXIO);
}
codec = AC97_CREATE(dev, csa, csa_ac97);
if (codec == NULL) {
csa_releaseres(csa, dev);
return (ENXIO);
}
if (csa->card->inv_eapd)
ac97_setflags(codec, AC97_F_EAPD_INV);
if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) {
ac97_destroy(codec);
csa_releaseres(csa, dev);
return (ENXIO);
}
snprintf(status, SND_STATUSLEN, "at irq %ld %s",
rman_get_start(resp->irq),PCM_KLDSTRING(snd_csa));
/* Enable interrupt. */
if (snd_setup_intr(dev, resp->irq, 0, csa_intr, csa, &csa->ih)) {
ac97_destroy(codec);
csa_releaseres(csa, dev);
return (ENXIO);
}
csa_writemem(resp, BA1_PFIE, csa_readmem(resp, BA1_PFIE) & ~0x0000f03f);
csa_writemem(resp, BA1_CIE, (csa_readmem(resp, BA1_CIE) & ~0x0000003f) | 0x00000001);
csa_active(csa, -1);
if (pcm_register(dev, csa, 1, 1)) {
ac97_destroy(codec);
csa_releaseres(csa, dev);
return (ENXIO);
}
pcm_addchan(dev, PCMDIR_REC, &csachan_class, csa);
pcm_addchan(dev, PCMDIR_PLAY, &csachan_class, csa);
pcm_setstatus(dev, status);
return (0);
}
static int
pcmcsa_detach(device_t dev)
{
int r;
struct csa_info *csa;
r = pcm_unregister(dev);
if (r)
return r;
csa = pcm_getdevinfo(dev);
csa_releaseres(csa, dev);
return 0;
}
static device_method_t pcmcsa_methods[] = {
/* Device interface */
DEVMETHOD(device_probe , pcmcsa_probe ),
DEVMETHOD(device_attach, pcmcsa_attach),
DEVMETHOD(device_detach, pcmcsa_detach),
{ 0, 0 },
};
static driver_t pcmcsa_driver = {
"pcm",
pcmcsa_methods,
PCM_SOFTC_SIZE,
};
DRIVER_MODULE(snd_csapcm, csa, pcmcsa_driver, pcm_devclass, 0, 0);
MODULE_DEPEND(snd_csapcm, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER);
MODULE_DEPEND(snd_csapcm, snd_csa, 1, 1, 1);
MODULE_VERSION(snd_csapcm, 1);