/* * Copyright (c) 2000, 2001, 2002 Alexander Matey * 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. */ /* * Part of this code was inspired by Aureal's linux driver */ #include #include "au88x0.h" #include #if __FreeBSD_version < 500000 #include #include #else #include #include #endif #include SND_DECLARE_FILE("$FreeBSD$"); #if __FreeBSD_version < 500033 #error Cannot be compiled on older 5.0-CURRENT and 4.x systems #endif #include "mixer_if.h" MALLOC_DEFINE(M_PCMAUCORE, "PCMaucore", "Aureal Vortex 88x0 core"); /* PCI IDs of supported chips */ #define AU8810_PCI_ID 0x000312eb #define AU8820_PCI_ID 0x000112eb #define AU8830_PCI_ID 0x000212eb /* maximum number of playback channels */ #define AU_MAXPLAYCH 4 /* chunks in DMA buffer */ #define AU_BUFFPAGES (AU_BUFFSIZE >> 12) /* Vortex Core interface */ typedef enum { ASPFMTLINEAR16 = 0, ASPFMTLINEAR8, ASPFMTULAW, ASPFMTALAW, ASPFMTSPORT, ASPFMTSPDIF, } ASPENCODING; typedef enum { ASPDIRPLAY, ASPDIRRECORD } ASPDIRECTION; typedef struct _aspwaveformat { u_short wBitWidth; u_short wChannels; u_long dwSampleRate; ASPENCODING eEncoding; } ASPWAVEFORMAT; #define MAX_MEM_REGISTERS 9 #define MAX_IO_PORTS 20 #define MAX_IRQS 7 #define MAX_DMA_CHANNELS 7 typedef struct _cmconfig { u_short wNumMemWindows; u_long dMemBase[MAX_MEM_REGISTERS]; u_long dMemLength[MAX_MEM_REGISTERS]; u_short wMemAttrib[MAX_MEM_REGISTERS]; u_short wNumIOPorts; u_short wIOPortBase[MAX_IO_PORTS]; u_short wIOPortLength[MAX_IO_PORTS]; u_short wNumIRQs; u_char bIRQRegisters[MAX_IRQS]; u_char bIRQAttrib[MAX_IRQS]; u_short wNumDMAs; u_char bDMALst[MAX_DMA_CHANNELS]; u_short wDMAAttrib[MAX_DMA_CHANNELS]; u_char bReserved1[3]; u_long BusNumber; } CMCONFIG; /* Timer info struct used by linux_(add|init|del)_timer() */ struct au_timer { u_long last_time; void (*func)(void *); void *arg; struct callout_handle handle; }; struct au_info; struct au_chinfo { u_int32_t total; /* total bytes processed */ int dir; /* channel direction */ int run; /* running? */ ASPWAVEFORMAT fmt; /* channel format */ u_int32_t chunk; /* active DMA buffer subchunk */ u_int32_t count; /* bytes processed in active subchunk */ struct au_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; void *wave; /* Vortex Core wave object */ }; /* Device softc struct */ struct au_info { SLIST_ENTRY(au_info) next; bus_space_tag_t st[3]; bus_space_handle_t sh[3]; bus_dma_tag_t parent_dmat; int regtype[3], regid[3], irqid; struct resource *reg[3], *irq; void *ih; void *core; /* Vortex Core object */ int dev_id; /* vortex chip id */ int unit; u_int32_t playchns; /* number of active playback channels */ struct au_chinfo chinfo[AU_MAXPLAYCH], recchinfo; void *lock; /* mutex lock for Vortex Core */ }; SLIST_HEAD(,au_info) au_info_head; static struct au_info *au_adding; /* used by au_pci_attach() */ /* Functions exported to Vortex Core */ void *linux_kmalloc(u_int size); void linux_kfree(void *p); void *linux_ioremap(u_long offset, u_long size); void linux_iounmap(void *addr); void linux_udelay(u_long usecs); void linux_dprintf(const char *fmt, ...); void *linux_memset(void *s, int c, u_long n); void *linux_memcpy(void *s, const void *ct, u_long n); u_int32_t linux_virt_to_phys(volatile void *addr); void linux_acquire_device_spinlock(u_long core_obj); void linux_release_device_spinlock(u_long core_obj); void linux_init_timer(u_long *timer, u_long func, u_long context); void linux_add_timer(u_long timer, u_long ms); void linux_del_timer(u_long timer); u_int32_t linux_jiffies(void); /* Functions imported from Vortex Core */ typedef void ClassAsp4Core; typedef void ClassWave; extern void *allocCore(CMCONFIG *pConfig); extern void deallocCore(ClassAsp4Core *pCore); extern u_long coreISR(ClassAsp4Core *pCore); extern void coreEnableInts(ClassAsp4Core *pCore); extern void coreDisableInts(ClassAsp4Core *pCore); extern void EnableHardCodedJoystickPort(ClassAsp4Core *pCore, int bEnable, u_long dwPort); extern int CreateWaveBuffer(ClassAsp4Core *pCore, ASPWAVEFORMAT *pWaveFormat, ASPDIRECTION eDirection, ClassWave **ppWaveObj); extern void StopWave(ClassWave *pWave); extern void ReleaseWave(ClassWave *pWave); extern void StartWave(ClassWave *pWave); extern u_long GetWaveBytesPlayed(ClassWave *pWave); extern int AddWaveBuffer(ClassWave *pWave, void *buf, u_long size); extern void SetWaveFormat(ClassWave *pWave, ASPWAVEFORMAT *pWaveFormat); extern void WriteCodec(ClassAsp4Core *pCore, short reg, short val); extern void ReadCodec(ClassAsp4Core *pCore, short reg, u_short *val); extern void WriteMixer(ClassAsp4Core *pCore, u_long id, u_long left, u_long right); /* Not using these yet, waiting for newmidi import */ extern void EnableHardCodedMPU401Port(ClassAsp4Core *pCore, int bEnable, u_long dwPort); extern void EnableMPU401Interrupt(ClassAsp4Core *pCore, int bEnable); extern void Mpu401UartWriteData(ClassAsp4Core *pCore, u_char bData); extern u_char Mpu401UartReadData(ClassAsp4Core *pCore); extern u_char Mpu401UartReadStatus(ClassAsp4Core *pCore); extern void Mpu401UartWriteCommand(ClassAsp4Core *pCore, u_char bData); extern int Mpu401UartInit(ClassAsp4Core *pCore, u_char bClockDivider, u_char bMidiMode, int bMpuMode); /* Prototypes */ static void au_intr(void *); static struct au_info *find_device_core(void *core_obj); static u_int32_t au_playfmt[] = { AFMT_MU_LAW, AFMT_STEREO | AFMT_MU_LAW, AFMT_A_LAW, AFMT_STEREO | AFMT_A_LAW, AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps au_playcaps = {4000, 48000, au_playfmt, 0}; static u_int32_t au_recfmt[] = { AFMT_MU_LAW, AFMT_STEREO | AFMT_MU_LAW, AFMT_A_LAW, AFMT_STEREO | AFMT_A_LAW, AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; static struct pcmchan_caps au_reccaps = {4000, 48000, au_recfmt, 0}; /* -------------------------------------------------------------------- */ static struct au_info * find_device_core(void *core_obj) { struct au_info *au, *i; au = NULL; SLIST_FOREACH(i, &au_info_head, next) { if (i->core == core_obj) au = i; } if (!au && au_adding) return au_adding; /* in case the card is being attached */ return au; } /* Functions exported to Vortex Core */ void * linux_kmalloc(u_int size) { return malloc(size, M_PCMAUCORE, M_NOWAIT); } void linux_kfree(void *p) { return free(p, M_PCMAUCORE); } void * linux_ioremap(u_long offset, u_long size) { struct au_info *au; int i; if (au_adding) for (i = 0; i < 3; i++) if (au_adding->regtype[i] == SYS_RES_MEMORY && offset == rman_get_start(au_adding->reg[i])) return rman_get_virtual(au_adding->reg[i]); SLIST_FOREACH(au, &au_info_head, next) { for (i = 0; i < 3; i++) if (au->regtype[i] == SYS_RES_MEMORY && offset == rman_get_start(au->reg[i])) return rman_get_virtual(au->reg[i]); } return NULL; } void linux_iounmap(void *addr) { /* do nothing */ } void linux_udelay(u_long usecs) { DELAY(usecs); } void linux_dprintf(const char *fmt, ...) { /* do nothing */ } void * linux_memset(void *s, int c, u_long n) { u_char *p; p = s; while (n--) *p++ = c; return s; } void * linux_memcpy(void *s, const void *ct, u_long n) { void *dst; dst = s; bcopy(ct, dst, n); return s; } u_int32_t linux_virt_to_phys(volatile void *addr) { return vtophys(addr); } void linux_acquire_device_spinlock(u_long core_obj) { struct au_info *au; if ((au = find_device_core((void *)core_obj)) != NULL) snd_mtxlock(au->lock); else /* couldn't find softc associated with this core */ printf("Vortex Core not found\n"); } void linux_release_device_spinlock(u_long core_obj) { struct au_info *au; if ((au = find_device_core((void *)core_obj)) != NULL) snd_mtxunlock(au->lock); else /* couldn't find softc associated with this core */ printf("Vortex Core not found\n"); } void linux_init_timer(u_long *timer, u_long func, u_long context) { struct au_timer *autimer; autimer = (struct au_timer *)malloc(sizeof(struct au_timer), M_DEVBUF, M_NOWAIT); bzero(autimer, sizeof(struct au_timer)); autimer->func = (void *)func; autimer->arg = (void *)context; *timer = (u_long)autimer; } void linux_add_timer(u_long timer, u_long ms) { struct au_timer *autimer; int expires; autimer = (struct au_timer *)timer; expires = (ms * hz) / 1000; if (expires + ticks > autimer->last_time) { autimer->last_time = expires + ticks; autimer->handle = timeout(autimer->func, autimer->arg, expires); } else ; /* late timer */ } void linux_del_timer(u_long timer) { struct au_timer *autimer; autimer = (struct au_timer *)timer; untimeout(autimer->func, autimer->arg, autimer->handle); free(autimer, M_DEVBUF); } u_int32_t linux_jiffies(void) { return ticks; } /* -------------------------------------------------------------------- */ /* Mixer interface */ static int aumix_init(struct snd_mixer *m) { mix_setdevs(m, AU_88XX_MIXER_DEVICES); mix_setrecdevs(m, AU_88XX_RECORDING_DEVICES); return 0; } static int aumix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct au_info *au; short reg; au = mix_getdevinfo(m); switch (dev) { case SOUND_MIXER_PCM: if (au->dev_id == AU8820_PCI_ID) { /* Must update codec pcm register on 8820 */ if (left < 7 && right < 7) { WriteCodec(au->core, AC97_MIX_PCM, AC97_MUTE); break; } else WriteCodec(au->core, AC97_MIX_PCM, 0x0606); } /* pcm scales 0 to 65535 */ WriteMixer(au->core, ASP_MIX_INPUT_WAVE_AT_PCM, (left * 655), (right * 655)); break; case SOUND_MIXER_LINE: case SOUND_MIXER_LINE1: case SOUND_MIXER_VIDEO: case SOUND_MIXER_VOLUME: case SOUND_MIXER_CD: switch (dev) { case SOUND_MIXER_LINE: reg = AC97_MIX_LINE; break; case SOUND_MIXER_LINE1: reg = AC97_MIX_AUX; break; case SOUND_MIXER_VIDEO: reg = AC97_MIX_VIDEO; break; case SOUND_MIXER_VOLUME: reg = AC97_MIX_MASTER; break; case SOUND_MIXER_CD: default: reg = AC97_MIX_CD; break; } if (left < 7 && right < 7) { WriteCodec(au->core, reg, AC97_MUTE); break; } if (left < 7) left = 7; if (right < 7) right = 7; /* codec scales 0x1f (-46db) to 0 (loudest) */ WriteCodec(au->core, reg, (((100 - left) / 3) << 8) | ((100 - right) / 3)); break; case SOUND_MIXER_TREBLE: case SOUND_MIXER_BASS: break; case SOUND_MIXER_PHONEOUT: case SOUND_MIXER_IGAIN: break; case SOUND_MIXER_MIC: left = right = min(left, right); /* scale between 0x5f and 0x40 (loudest) */ WriteCodec(au->core, AC97_MIX_MIC, (left < 7) ? AC97_MUTE : ((100 - left) / 3) | 0x40); break; case SOUND_MIXER_SPEAKER: left = right = min(left, right); WriteCodec(au->core, AC97_MIX_BEEP, (left < 10) ? AC97_MUTE : ((100 - left) / 6) << 1); break; case SOUND_MIXER_OGAIN: left = right = min(left, right); WriteCodec(au->core, AC97_MIX_MONO, (left < 7) ? AC97_MUTE : (100 - left) / 3); break; case SOUND_MIXER_PHONEIN: left = right = min(left, right); WriteCodec(au->core, AC97_MIX_PHONE, (left < 7) ? AC97_MUTE : (100 - left) / 3); break; case SOUND_MIXER_RECLEV: if (left < 10 || right < 10) { WriteCodec(au->core, AC97_MIX_RGAIN, AC97_MUTE); WriteMixer(au->core, ASP_MIX_INPUT_WAVE_AT_WAVEIN, 0, 0); break; } if (left < 10) left = 10; if (right < 10) right = 10; WriteCodec(au->core, AC97_MIX_RGAIN, (((left - 10) / 6) << 8) | ((right - 10) / 6)); WriteMixer(au->core, ASP_MIX_INPUT_WAVE_AT_WAVEIN, (left * 655), (right * 655)); break; default: break; } return left | (right << 8); } static int aumix_setrecsrc(struct snd_mixer *m, u_int32_t src) { struct au_info *au; short val; au = mix_getdevinfo(m); switch (src) { case SOUND_MASK_CD: val = 0x1; break; case SOUND_MASK_VIDEO: val = 0x2; break; case SOUND_MASK_LINE1: val = 0x3; break; case SOUND_MASK_LINE: val = 0x4; break; case SOUND_MASK_VOLUME: val = 0x5; break; case SOUND_MASK_PHONEOUT: val = 0x6; break; case SOUND_MASK_PHONEIN: val = 0x7; break; case SOUND_MASK_MIC: default: val = 0; break; } WriteCodec(au->core, AC97_REG_RECSEL, (val << 8 | val)); return src; } static kobj_method_t aumixer_methods[] = { KOBJMETHOD(mixer_init, aumix_init), KOBJMETHOD(mixer_set, aumix_set), KOBJMETHOD(mixer_setrecsrc, aumix_setrecsrc), { 0, 0 } }; MIXER_DECLARE(aumixer); /* -------------------------------------------------------------------- */ /* Channel interface */ static void * auchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct au_info *au = devinfo; struct au_chinfo *ch; if (dir == PCMDIR_PLAY) { ch = &au->chinfo[au->playchns++]; ch->fmt.eEncoding = ASPFMTLINEAR8; ch->fmt.wBitWidth = 8; ch->fmt.wChannels = 1; ch->fmt.dwSampleRate = 8000; if (CreateWaveBuffer(au->core, &ch->fmt, ASPDIRPLAY, &ch->wave) != 0) { printf("cannot init playback channel #%u\n", au->playchns); au->playchns--; return NULL; } } else { ch = &au->recchinfo; ch->fmt.eEncoding = ASPFMTLINEAR8; ch->fmt.wBitWidth = 8; ch->fmt.wChannels = 1; ch->fmt.dwSampleRate = 8000; if (CreateWaveBuffer(au->core, &ch->fmt, ASPDIRRECORD, &ch->wave) != 0) { printf("cannot init recording channel\n"); return NULL; } } ch->dir = dir; ch->parent = au; ch->channel = c; ch->buffer = b; ch->run = 0; if (sndbuf_alloc(ch->buffer, au->parent_dmat, #if __FreeBSD_version >= 700037 0, #endif AU_BUFFSIZE) != 0) { printf("sndbuf_alloc failed\n"); return NULL; } ch->count = ch->chunk = ch->total = 0; return ch; } static int auchan_setformat(kobj_t obj, void *data, u_int32_t format) { struct au_chinfo *ch = data; ASPWAVEFORMAT *fmt = &ch->fmt; fmt->wBitWidth = 8; if (format & AFMT_S16_LE) { fmt->eEncoding = ASPFMTLINEAR16; fmt->wBitWidth = 16; } else if (format & AFMT_U8) fmt->eEncoding = ASPFMTLINEAR8; else if (format & AFMT_A_LAW) fmt->eEncoding = ASPFMTALAW; else if (format & AFMT_MU_LAW) fmt->eEncoding = ASPFMTULAW; if (format & AFMT_STEREO) fmt->wChannels = 2; else fmt->wChannels = 1; SetWaveFormat(ch->wave, &ch->fmt); return 0; } static int auchan_setspeed(kobj_t obj, void *data, u_int32_t speed) { struct au_chinfo *ch = data; ch->fmt.dwSampleRate = speed; SetWaveFormat(ch->wave, &ch->fmt); return speed; } static int auchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { return blocksize; } static int auchan_trigger(kobj_t obj, void *data, int go) { struct au_chinfo *ch = data; int i; if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { ch->run = 1; for (i = 0; i < AU_BUFFPAGES; i++) AddWaveBuffer(ch->wave, (char *)sndbuf_getbuf(ch->buffer) + (i << 12), PAGE_SIZE); StartWave(ch->wave); } else { ch->run = 0; StopWave(ch->wave); ch->count = ch->chunk = ch->total = 0; } } else { if (go == PCMTRIG_START) { ch->run = 1; AddWaveBuffer(ch->wave, sndbuf_getbuf(ch->buffer), PAGE_SIZE); AddWaveBuffer(ch->wave, (char *)sndbuf_getbuf(ch->buffer)+PAGE_SIZE, PAGE_SIZE); StartWave(ch->wave); } else { ch->run = 0; StopWave(ch->wave); ch->count = ch->chunk = ch->total = 0; } } return 0; } static int auchan_getptr(kobj_t obj, void *data) { struct au_chinfo *ch = data; if (ch->dir == PCMDIR_REC) /* * On recording Vortex Core seems to return number of * bytes written ahead of what it's actually stored in DMA * buffer. Thus, delay DMA buffer pointer. */ return ((ch->chunk << 12) + ch->count) & (AU_BUFFSIZE-1) & ~0x3ff; else return ((ch->chunk << 12) + ch->count) & (AU_BUFFSIZE-1) & ~0x03; } static struct pcmchan_caps * auchan_getcaps(kobj_t obj, void *data) { struct au_chinfo *ch = data; return (ch->dir == PCMDIR_PLAY) ? &au_playcaps : &au_reccaps; } static kobj_method_t auchan_methods[] = { KOBJMETHOD(channel_init, auchan_init), KOBJMETHOD(channel_setformat, auchan_setformat), KOBJMETHOD(channel_setspeed, auchan_setspeed), KOBJMETHOD(channel_setblocksize, auchan_setblocksize), KOBJMETHOD(channel_trigger, auchan_trigger), KOBJMETHOD(channel_getptr, auchan_getptr), KOBJMETHOD(channel_getcaps, auchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(auchan); /* -------------------------------------------------------------------- */ /* Interrupt helper */ static void au_intr_helper(struct au_chinfo *ch) { u_int32_t played; played = GetWaveBytesPlayed(ch->wave); ch->count += played; ch->total += played; if (ch->dir == PCMDIR_PLAY) { if (ch->count >= PAGE_SIZE) { /* let Vortex Core reuse this chunk */ AddWaveBuffer(ch->wave, (char *)sndbuf_getbuf(ch->buffer) + (ch->chunk << 12), PAGE_SIZE); ch->count -= PAGE_SIZE; ch->chunk++; ch->chunk &= (AU_BUFFPAGES-1); chn_intr(ch->channel); } } else { if (ch->count >= PAGE_SIZE) { ch->count -= PAGE_SIZE; ch->chunk++; ch->chunk &= (AU_BUFFPAGES-1); /* have one extra available chunk at all times */ AddWaveBuffer(ch->wave, (char *)sndbuf_getbuf(ch->buffer) + (((ch->chunk+1) & (AU_BUFFPAGES-1)) << 12), PAGE_SIZE); } /* always pass recording interrupts down to sound subsystem w/o delays */ chn_intr(ch->channel); } } /* The interrupt handler */ static void au_intr (void *p) { struct au_info *au = p; u_int32_t irq_status; int i; irq_status = coreISR(au->core); /* shared interrupt - return if not ours */ if (!irq_status) return; if (irq_status & 0x1000) { if (au->recchinfo.run) au_intr_helper(&au->recchinfo); for (i = 0; i < AU_MAXPLAYCH; i++) if (au->chinfo[i].run) au_intr_helper(&au->chinfo[i]); } } /* -------------------------------------------------------------------- */ /* Probe and attach the card */ static int au_pci_probe(device_t dev) { char *s = NULL; switch (pci_get_devid(dev)) { case AU8810_PCI_ID: s = "Aureal Vortex 8810"; break; case AU8820_PCI_ID: s = "Aureal Vortex 8820"; break; case AU8830_PCI_ID: s = "Aureal Vortex 8830"; break; } if (s) device_set_desc(dev, s); return s ? BUS_PROBE_DEFAULT : ENXIO; } static int au_pci_attach(device_t dev) { struct au_info *au; u_int32_t data; CMCONFIG config; int i; char status[SND_STATUSLEN]; if ((au = malloc(sizeof(*au), M_DEVBUF, M_NOWAIT)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return ENXIO; } bzero(au, sizeof(*au)); au->unit = device_get_unit(dev); au->dev_id = pci_get_devid(dev); au->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); /* init softc list */ SLIST_INIT(&au_info_head); /* to make linux-*-spinlock() work before we add au onto the list */ au_adding = au; data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); for (i = 0; i < 3; i++) { au->regid[i] = PCIR_BAR(i); au->regtype[i] = SYS_RES_MEMORY; au->reg[i] = bus_alloc_resource(dev, au->regtype[i], &au->regid[i], 0, ~0, 1, RF_ACTIVE); if (!au->reg[i]) { au->regtype[i] = SYS_RES_IOPORT; au->reg[i] = bus_alloc_resource(dev, au->regtype[i], &au->regid[i], 0, ~0, 1, RF_ACTIVE); } if (au->reg[i]) { au->st[i] = rman_get_bustag(au->reg[i]); au->sh[i] = rman_get_bushandle(au->reg[i]); } } au->irqid = 0; au->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &au->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!au->irq || snd_setup_intr(dev, au->irq, INTR_MPSAFE, au_intr, au, &au->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } bzero(&config, sizeof(CMCONFIG)); config.dMemBase[0] = rman_get_start(au->reg[0]); config.dMemLength[0] = au->dev_id == AU8820_PCI_ID ? 0x20000 : 0x40000; config.wIOPortBase[0] = rman_get_start(au->reg[1]); config.wIOPortLength[0] = 2; config.wIOPortBase[1] = rman_get_start(au->reg[2]); config.wIOPortLength[1] = 2; config.bIRQRegisters[0] = rman_get_start(au->irq); config.bIRQAttrib[0] = 0; config.BusNumber = device_get_unit(device_get_parent(dev)); /* allocate Vortex core */ if ((au->core = allocCore(&config)) == NULL) { device_printf(dev, "could not allocate core\n"); goto bad; } /* enable interrupts */ coreEnableInts(au->core); /* init mixer */ if (mixer_init(dev, &aumixer_class, au)) goto bad; if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, /*maxsize*/AU_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, #if __FreeBSD_version > 501102 /*lockfunc*/NULL, /*lockarg*/NULL, #endif &au->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld", (au->regtype[0] == SYS_RES_IOPORT)? "io" : "memory", rman_get_start(au->reg[0]), rman_get_start(au->irq)); if (pcm_register(dev, au, AU_MAXPLAYCH, 1)) goto bad; pcm_addchan(dev, PCMDIR_REC, &auchan_class, au); for (i = 0; i < AU_MAXPLAYCH; i++) pcm_addchan(dev, PCMDIR_PLAY, &auchan_class, au); pcm_setstatus(dev, status); /* link to the device list */ SLIST_INSERT_HEAD(&au_info_head, au, next); au_adding = NULL; return 0; bad: if (au && au->core) { coreDisableInts(au->core); deallocCore(au->core); } for (i = 0; i < 3; i++) bus_release_resource(dev, au->regtype[i], au->regid[i], au->reg[i]); if (au->ih) bus_teardown_intr(dev, au->irq, au->ih); if (au->irq) bus_release_resource(dev, SYS_RES_IRQ, au->irqid, au->irq); if (au) free(au, M_DEVBUF); if (au->lock) snd_mtxfree(au->lock); au_adding = NULL; return ENXIO; } static int au_pci_detach(device_t dev) { int i; struct au_info *au; i = pcm_unregister(dev); if (i) return i; au = pcm_getdevinfo(dev); /* shutdown chip */ coreDisableInts(au->core); deallocCore(au->core); for (i = 0; i < 3; i++) bus_release_resource(dev, au->regtype[i], au->regid[i], au->reg[i]); if (au->ih) bus_teardown_intr(dev, au->irq, au->ih); if (au->irq) bus_release_resource(dev, SYS_RES_IRQ, au->irqid, au->irq); if (au->parent_dmat) bus_dma_tag_destroy(au->parent_dmat); SLIST_REMOVE(&au_info_head, au, au_info, next); if (au->lock) snd_mtxfree(au->lock); free(au, M_DEVBUF); return 0; } static device_method_t au_methods[] = { /* Device interface */ DEVMETHOD(device_probe, au_pci_probe), DEVMETHOD(device_attach, au_pci_attach), DEVMETHOD(device_detach, au_pci_detach), { 0, 0 } }; static driver_t au_driver = { "pcm", au_methods, PCM_SOFTC_SIZE }; DRIVER_MODULE(snd_au88x0, pci, au_driver, pcm_devclass, 0, 0); #if __FreeBSD_version > 502124 MODULE_DEPEND(snd_au88x0, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); #else MODULE_DEPEND(snd_au88x0, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); #endif MODULE_VERSION(snd_au88x0, 1);