From 9b6cff350c65b24735e8c99c37e78e8e2aeeca8f Mon Sep 17 00:00:00 2001 From: orion Date: Mon, 23 Apr 2001 21:53:12 +0000 Subject: Initial version of Avance Logic ALS4000 pcm driver. --- sys/conf/files | 1 + sys/dev/sound/driver.c | 1 + sys/dev/sound/pci/als4000.c | 898 ++++++++++++++++++++++++++++++ sys/dev/sound/pci/als4000.h | 96 ++++ sys/modules/sound/driver/als4000/Makefile | 9 + 5 files changed, 1005 insertions(+) create mode 100644 sys/dev/sound/pci/als4000.c create mode 100644 sys/dev/sound/pci/als4000.h create mode 100644 sys/modules/sound/driver/als4000/Makefile (limited to 'sys') diff --git a/sys/conf/files b/sys/conf/files index 3d6e793..8aac5a8 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -478,6 +478,7 @@ dev/sound/midi/midi.c optional midi dev/sound/midi/midibuf.c optional midi dev/sound/midi/midisynth.c optional midi dev/sound/midi/sequencer.c optional seq midi +dev/sound/pci/als4000.c optional pcm pci #dev/sound/pci/aureal.c optional pcm pci dev/sound/pci/cmi.c optional pcm pci dev/sound/pci/cs4281.c optional pcm pci diff --git a/sys/dev/sound/driver.c b/sys/dev/sound/driver.c index 58f3033..4c2e098 100644 --- a/sys/dev/sound/driver.c +++ b/sys/dev/sound/driver.c @@ -52,6 +52,7 @@ DECLARE_MODULE(snd_driver, snd_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(snd_driver, 1); MODULE_DEPEND(snd_driver, snd_ad1816, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_als4000, 1, 1, 1); /* MODULE_DEPEND(snd_driver, snd_aureal, 1, 1, 1); */ MODULE_DEPEND(snd_driver, snd_cmi, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_cs4281, 1, 1, 1); diff --git a/sys/dev/sound/pci/als4000.c b/sys/dev/sound/pci/als4000.c new file mode 100644 index 0000000..c9e3198 --- /dev/null +++ b/sys/dev/sound/pci/als4000.c @@ -0,0 +1,898 @@ +/* + * Copyright (c) 2001 Orion Hodson + * 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * als4000.c - driver for the Avance Logic ALS 4000 chipset. + * + * The ALS4000 is a effectively an SB16 with a PCI interface. + * + * This driver derives from ALS4000a.PDF, Bart Hartgers alsa driver, and + * SB16 register descriptions. + */ + +#include +#include +#include + +#include +#include + +#include "mixer_if.h" + +/* Debugging macro's */ +#undef DEB +#ifndef DEB +#define DEB(x) /* x */ +#endif /* DEB */ + +/* ------------------------------------------------------------------------- */ +/* Structures */ + +struct sc_info; + +struct sc_chinfo { + struct sc_info *parent; + struct pcm_channel *channel; + struct snd_dbuf *buffer; + u_int32_t format, speed, phys_buf, bps; + u_int32_t dma_active:1, dma_was_active:1; + u_int8_t gcr_fifo_status; + int dir; +}; + +struct sc_info { + device_t dev; + bus_space_tag_t st; + bus_space_handle_t sh; + bus_dma_tag_t parent_dmat; + struct resource *reg, *irq; + int regid, irqid; + void *ih; + struct sc_chinfo pch, rch; +}; + +/* Channel caps */ + +static u_int32_t als_format[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + 0 +}; + +static struct pcmchan_caps als_caps = { 4000, 48000, als_format, 0 }; + +/* ------------------------------------------------------------------------- */ +/* Register Utilities */ + +static u_int32_t +als_gcr_rd(struct sc_info *sc, int index) +{ + bus_space_write_1(sc->st, sc->sh, ALS_GCR_INDEX, index); + return bus_space_read_4(sc->st, sc->sh, ALS_GCR_DATA); +} + +static void +als_gcr_wr(struct sc_info *sc, int index, int data) +{ + bus_space_write_1(sc->st, sc->sh, ALS_GCR_INDEX, index); + bus_space_write_4(sc->st, sc->sh, ALS_GCR_DATA, data); +} + +static u_int8_t +als_intr_rd(struct sc_info *sc) +{ + return bus_space_read_1(sc->st, sc->sh, ALS_SB_MPU_IRQ); +} + +static void +als_intr_wr(struct sc_info *sc, u_int8_t data) +{ + bus_space_write_1(sc->st, sc->sh, ALS_SB_MPU_IRQ, data); +} + +static u_int8_t +als_mix_rd(struct sc_info *sc, u_int8_t index) +{ + bus_space_write_1(sc->st, sc->sh, ALS_MIXER_INDEX, index); + return bus_space_read_1(sc->st, sc->sh, ALS_MIXER_DATA); +} + +static void +als_mix_wr(struct sc_info *sc, u_int8_t index, u_int8_t data) +{ + bus_space_write_1(sc->st, sc->sh, ALS_MIXER_INDEX, index); + bus_space_write_1(sc->st, sc->sh, ALS_MIXER_DATA, data); +} + +static void +als_esp_wr(struct sc_info *sc, u_int8_t data) +{ + u_int32_t tries, v; + + tries = 1000; + do { + v = bus_space_read_1(sc->st, sc->sh, ALS_ESP_WR_STATUS); + if (~v & 0x80) + break; + DELAY(20); + } while (--tries != 0); + + if (tries == 0) + device_printf(sc->dev, "als_esp_wr timeout"); + + bus_space_write_1(sc->st, sc->sh, ALS_ESP_WR_DATA, data); +} + +static int +als_esp_reset(struct sc_info *sc) +{ + u_int32_t tries, u, v; + + bus_space_write_1(sc->st, sc->sh, ALS_ESP_RST, 1); + DELAY(10); + bus_space_write_1(sc->st, sc->sh, ALS_ESP_RST, 0); + DELAY(30); + + tries = 1000; + do { + u = bus_space_read_1(sc->st, sc->sh, ALS_ESP_RD_STATUS8); + if (u & 0x80) { + v = bus_space_read_1(sc->st, sc->sh, ALS_ESP_RD_DATA); + if (v == 0xaa) + return 0; + else + break; + } + DELAY(20); + } while (--tries != 0); + + if (tries == 0) + device_printf(sc->dev, "als_esp_reset timeout"); + return 1; +} + +static u_int8_t +als_ack_read(struct sc_info *sc, u_int8_t addr) +{ + u_int8_t r = bus_space_read_1(sc->st, sc->sh, addr); + return r; +} + +/* ------------------------------------------------------------------------- */ +/* Common pcm channel implementation */ + +static void * +alschan_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; + + if (dir == PCMDIR_PLAY) { + ch = &sc->pch; + ch->gcr_fifo_status = ALS_GCR_FIFO0_STATUS; + } else { + ch = &sc->rch; + ch->gcr_fifo_status = ALS_GCR_FIFO1_STATUS; + } + ch->dir = dir; + ch->parent = sc; + ch->channel = c; + ch->bps = 1; + ch->format = AFMT_U8; + ch->speed = DSP_DEFAULT_SPEED; + ch->buffer = b; + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, ALS_BUFFER_SIZE) != 0) { + return NULL; + } + return ch; +} + +static int +alschan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct sc_chinfo *ch = data; + + ch->format = format; + return 0; +} + +static int +alschan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct sc_chinfo *ch = data, *other; + struct sc_info *sc = ch->parent; + + other = (ch->dir == PCMDIR_PLAY) ? &sc->rch : &sc->pch; + + /* Deny request if other dma channel is active */ + if (other->dma_active) { + ch->speed = other->speed; + return other->speed; + } + + ch->speed = speed; + return speed; +} + +static int +alschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct sc_chinfo *ch = data; + + if (blocksize > ALS_BUFFER_SIZE / 2) { + blocksize = ALS_BUFFER_SIZE / 2; + } + sndbuf_resize(ch->buffer, 2, blocksize); + return sndbuf_getsize(ch->buffer); +} + +static int +alschan_getptr(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + int32_t pos, sz; + + pos = als_gcr_rd(ch->parent, ch->gcr_fifo_status) & 0xffff; + sz = sndbuf_getsize(ch->buffer); + return (2 * sz - pos - 1) % sz; +} + +static struct pcmchan_caps* +alschan_getcaps(kobj_t obj, void *data) +{ + return &als_caps; +} + +static void +als_set_speed(struct sc_chinfo *ch) +{ + struct sc_info *sc = ch->parent; + struct sc_chinfo *other; + + other = (ch->dir == PCMDIR_PLAY) ? &sc->rch : &sc->pch; + if (other->dma_active == 0) { + als_esp_wr(sc, ALS_ESP_SAMPLE_RATE); + als_esp_wr(sc, ch->speed >> 8); + als_esp_wr(sc, ch->speed & 0xff); + } else { + DEB(printf("speed locked at %d (tried %d)\n", + other->speed, ch->speed)); + } +} + +/* ------------------------------------------------------------------------- */ +/* Playback channel implementation */ + +#define ALS_8BIT_CMD(x, y) { (x), (y), DSP_DMA8, DSP_CMD_DMAPAUSE_8 } +#define ALS_16BIT_CMD(x, y) { (x), (y), DSP_DMA16, DSP_CMD_DMAPAUSE_16 } + +struct playback_command { + u_int32_t pcm_format; /* newpcm format */ + u_int8_t format_val; /* sb16 format value */ + u_int8_t dma_prog; /* sb16 dma program */ + u_int8_t dma_stop; /* sb16 stop register */ +} static const playback_cmds[] = { + ALS_8BIT_CMD(AFMT_U8, DSP_MODE_U8MONO), + ALS_8BIT_CMD(AFMT_U8 | AFMT_STEREO, DSP_MODE_U8STEREO), + ALS_16BIT_CMD(AFMT_S16_LE, DSP_MODE_S16MONO), + ALS_16BIT_CMD(AFMT_S16_LE | AFMT_STEREO, DSP_MODE_S16STEREO), +}; + +static const struct playback_command* +als_get_playback_command(u_int32_t format) +{ + u_int32_t i, n; + + n = sizeof(playback_cmds) / sizeof(playback_cmds[0]); + for (i = 0; i < n; i++) { + if (playback_cmds[i].pcm_format == format) { + return &playback_cmds[i]; + } + } + DEB(printf("als_get_playback_command: invalid format 0x%08x\n", + format)); + return &playback_cmds[0]; +} + +static void +als_playback_start(struct sc_chinfo *ch) +{ + const struct playback_command *p; + struct sc_info *sc = ch->parent; + u_int32_t buf, bufsz, count, dma_prog; + + buf = vtophys(sndbuf_getbuf(ch->buffer)); + bufsz = sndbuf_getsize(ch->buffer); + count = bufsz / 2; + if (ch->format & AFMT_16BIT) + count /= 2; + count--; + + als_esp_wr(sc, DSP_CMD_SPKON); + als_set_speed(ch); + + als_gcr_wr(sc, ALS_GCR_DMA0_START, buf); + als_gcr_wr(sc, ALS_GCR_DMA0_MODE, (bufsz - 1) | 0x180000); + + p = als_get_playback_command(ch->format); + dma_prog = p->dma_prog | DSP_F16_DAC | DSP_F16_AUTO | DSP_F16_FIFO_ON; + + als_esp_wr(sc, dma_prog); + als_esp_wr(sc, p->format_val); + als_esp_wr(sc, count & 0xff); + als_esp_wr(sc, count >> 8); + + ch->dma_active = 1; +} + +static int +als_playback_stop(struct sc_chinfo *ch) +{ + const struct playback_command *p; + struct sc_info *sc = ch->parent; + u_int32_t active; + + active = ch->dma_active; + if (active) { + p = als_get_playback_command(ch->format); + als_esp_wr(sc, p->dma_stop); + } + ch->dma_active = 0; + return active; +} + +static int +alspchan_trigger(kobj_t obj, void *data, int go) +{ + struct sc_chinfo *ch = data; + + switch(go) { + case PCMTRIG_START: + als_playback_start(ch); + break; + case PCMTRIG_ABORT: + als_playback_stop(ch); + break; + } + return 0; +} + +static kobj_method_t alspchan_methods[] = { + KOBJMETHOD(channel_init, alschan_init), + KOBJMETHOD(channel_setformat, alschan_setformat), + KOBJMETHOD(channel_setspeed, alschan_setspeed), + KOBJMETHOD(channel_setblocksize, alschan_setblocksize), + KOBJMETHOD(channel_trigger, alspchan_trigger), + KOBJMETHOD(channel_getptr, alschan_getptr), + KOBJMETHOD(channel_getcaps, alschan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(alspchan); + +/* ------------------------------------------------------------------------- */ +/* Capture channel implementation */ + +static u_int8_t +als_get_fifo_format(struct sc_info *sc, u_int32_t format) +{ + switch (format) { + case AFMT_U8: + return ALS_FIFO1_8BIT; + case AFMT_U8 | AFMT_STEREO: + return ALS_FIFO1_8BIT | ALS_FIFO1_STEREO; + case AFMT_S16_LE: + return ALS_FIFO1_SIGNED; + case AFMT_S16_LE | AFMT_STEREO: + return ALS_FIFO1_SIGNED | ALS_FIFO1_STEREO; + } + device_printf(sc->dev, "format not found: 0x%08x\n", format); + return ALS_FIFO1_8BIT; +} + +static void +als_capture_start(struct sc_chinfo *ch) +{ + struct sc_info *sc = ch->parent; + u_int32_t buf, bufsz, count, dma_prog; + + buf = vtophys(sndbuf_getbuf(ch->buffer)); + bufsz = sndbuf_getsize(ch->buffer); + count = bufsz / 2; + if (ch->format & AFMT_16BIT) + count /= 2; + count--; + + als_esp_wr(sc, DSP_CMD_SPKON); + als_set_speed(ch); + + als_gcr_wr(sc, ALS_GCR_FIFO1_START, buf); + als_gcr_wr(sc, ALS_GCR_FIFO1_COUNT, (bufsz - 1)); + + als_mix_wr(sc, ALS_FIFO1_LENGTH_LO, count & 0xff); + als_mix_wr(sc, ALS_FIFO1_LENGTH_HI, count >> 8); + + dma_prog = ALS_FIFO1_RUN | als_get_fifo_format(sc, ch->format); + als_mix_wr(sc, ALS_FIFO1_CONTROL, dma_prog); + + ch->dma_active = 1; +} + +static int +als_capture_stop(struct sc_chinfo *ch) +{ + struct sc_info *sc = ch->parent; + u_int32_t active; + + active = ch->dma_active; + if (active) { + als_mix_wr(sc, ALS_FIFO1_CONTROL, ALS_FIFO1_STOP); + } + ch->dma_active = 0; + return active; +} + +static int +alsrchan_trigger(kobj_t obj, void *data, int go) +{ + struct sc_chinfo *ch = data; + + switch(go) { + case PCMTRIG_START: + als_capture_start(ch); + break; + case PCMTRIG_ABORT: + als_capture_stop(ch); + break; + } + return 0; +} + +static kobj_method_t alsrchan_methods[] = { + KOBJMETHOD(channel_init, alschan_init), + KOBJMETHOD(channel_setformat, alschan_setformat), + KOBJMETHOD(channel_setspeed, alschan_setspeed), + KOBJMETHOD(channel_setblocksize, alschan_setblocksize), + KOBJMETHOD(channel_trigger, alsrchan_trigger), + KOBJMETHOD(channel_getptr, alschan_getptr), + KOBJMETHOD(channel_getcaps, alschan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(alsrchan); + +/* ------------------------------------------------------------------------- */ +/* Mixer related */ + +/* + * ALS4000 has an sb16 mixer, with some additional controls that we do + * not yet a means to support. + */ + +struct sb16props { + u_int8_t lreg; + u_int8_t rreg; + u_int8_t bits; + u_int8_t oselect; + u_int8_t iselect; /* left input mask */ +} static const amt[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = { 0x30, 0x31, 5, 0x00, 0x00 }, + [SOUND_MIXER_PCM] = { 0x32, 0x33, 5, 0x00, 0x00 }, + [SOUND_MIXER_SYNTH] = { 0x34, 0x35, 5, 0x60, 0x40 }, + [SOUND_MIXER_CD] = { 0x36, 0x37, 5, 0x06, 0x04 }, + [SOUND_MIXER_LINE] = { 0x38, 0x39, 5, 0x18, 0x10 }, + [SOUND_MIXER_MIC] = { 0x3a, 0x00, 5, 0x01, 0x01 }, + [SOUND_MIXER_SPEAKER] = { 0x3b, 0x00, 2, 0x00, 0x00 }, + [SOUND_MIXER_IGAIN] = { 0x3f, 0x40, 2, 0x00, 0x00 }, + [SOUND_MIXER_OGAIN] = { 0x41, 0x42, 2, 0x00, 0x00 }, + /* The following have register values but no h/w implementation */ + [SOUND_MIXER_TREBLE] = { 0x44, 0x45, 4, 0x00, 0x00 }, + [SOUND_MIXER_BASS] = { 0x46, 0x47, 4, 0x00, 0x00 } +}; + +static int +alsmix_init(struct snd_mixer *m) +{ + u_int32_t i, v; + + for (i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (amt[i].bits) v |= 1 << i; + } + mix_setdevs(m, v); + + for (i = v = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (amt[i].iselect) v |= 1 << i; + } + mix_setrecdevs(m, v); + return 0; +} + +static int +alsmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct sc_info *sc = mix_getdevinfo(m); + u_int32_t r, l, v, mask; + + /* Fill upper n bits in mask with 1's */ + mask = ((1 << amt[dev].bits) - 1) << (8 - amt[dev].bits); + + l = (left * mask / 100) & mask; + v = als_mix_rd(sc, amt[dev].lreg) & ~mask; + als_mix_wr(sc, amt[dev].lreg, l | v); + + if (amt[dev].rreg) { + r = (right * mask / 100) & mask; + v = als_mix_rd(sc, amt[dev].rreg) & ~mask; + als_mix_wr(sc, amt[dev].rreg, r | v); + } else { + r = 0; + } + + /* Zero gain does not mute channel from output, but this does. */ + v = als_mix_rd(sc, SB16_OMASK); + if (l == 0 && r == 0) { + v &= ~amt[dev].oselect; + } else { + v |= amt[dev].oselect; + } + als_mix_wr(sc, SB16_OMASK, v); + return 0; +} + +static int +alsmix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct sc_info *sc = mix_getdevinfo(m); + u_int32_t i, l, r; + + for (i = l = r = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (src & (1 << i)) { + l |= amt[i].iselect; + r |= amt[i].iselect << 1; + } + } + + als_mix_wr(sc, SB16_IMASK_L, l); + als_mix_wr(sc, SB16_IMASK_R, r); + return src; +} + +static kobj_method_t als_mixer_methods[] = { + KOBJMETHOD(mixer_init, alsmix_init), + KOBJMETHOD(mixer_set, alsmix_set), + KOBJMETHOD(mixer_setrecsrc, alsmix_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(als_mixer); + +/* ------------------------------------------------------------------------- */ +/* Interrupt Handler */ + +static void +als_intr(void *p) +{ + struct sc_info *sc = (struct sc_info *)p; + u_int8_t intr, sb_status; + + intr = als_intr_rd(sc); + + if (intr & 0x80) + chn_intr(sc->pch.channel); + + if (intr & 0x40) + chn_intr(sc->rch.channel); + + /* ACK interrupt in PCI core */ + als_intr_wr(sc, intr); + + /* ACK interrupt in SB core */ + sb_status = als_mix_rd(sc, IRQ_STAT); + + if (sb_status & ALS_IRQ_STATUS8) + als_ack_read(sc, ALS_ESP_RD_STATUS8); + if (sb_status & ALS_IRQ_STATUS16) + als_ack_read(sc, ALS_ESP_RD_STATUS16); + if (sb_status & ALS_IRQ_MPUIN) + als_ack_read(sc, ALS_MIDI_DATA); + if (sb_status & ALS_IRQ_CR1E) + als_ack_read(sc, ALS_CR1E_ACK_PORT); + return; +} + +/* ------------------------------------------------------------------------- */ +/* H/W initialization */ + +static int +als_init(struct sc_info *sc) +{ + u_int32_t i, v; + + /* Reset Chip */ + if (als_esp_reset(sc)) { + return 1; + } + + /* Enable write on DMA_SETUP register */ + v = als_mix_rd(sc, ALS_SB16_CONFIG); + als_mix_wr(sc, ALS_SB16_CONFIG, v | 0x80); + + /* Select DMA0 */ + als_mix_wr(sc, ALS_SB16_DMA_SETUP, 0x01); + + /* Disable write on DMA_SETUP register */ + als_mix_wr(sc, ALS_SB16_CONFIG, v & 0x7f); + + /* Enable interrupts */ + v = als_gcr_rd(sc, ALS_GCR_MISC); + als_gcr_wr(sc, ALS_GCR_MISC, v | 0x28000); + + /* Black out GCR DMA registers */ + for (i = 0x91; i <= 0x96; i++) { + als_gcr_wr(sc, i, 0); + } + + /* Emulation mode */ + v = als_gcr_rd(sc, ALS_GCR_DMA_EMULATION); + als_gcr_wr(sc, ALS_GCR_DMA_EMULATION, v); + DEB(printf("GCR_DMA_EMULATION 0x%08x\n", v)); + return 0; +} + +static void +als_uninit(struct sc_info *sc) +{ + /* Disable interrupts */ + als_gcr_wr(sc, ALS_GCR_MISC, 0); +} + +/* ------------------------------------------------------------------------- */ +/* Probe and attach card */ + +static int +als_pci_probe(device_t dev) +{ + if (pci_get_devid(dev) == ALS_PCI_ID0) { + device_set_desc(dev, "Avance Logic ALS4000"); + return 0; + } + return ENXIO; +} + +static void +als_resource_free(device_t dev, struct sc_info *sc) +{ + if (sc->reg) { + bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); + sc->reg = 0; + } + if (sc->ih) { + bus_teardown_intr(dev, sc->irq, sc->ih); + sc->ih = 0; + } + if (sc->irq) { + bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + sc->irq = 0; + } + if (sc->parent_dmat) { + bus_dma_tag_destroy(sc->parent_dmat); + sc->parent_dmat = 0; + } +} + +static int +als_resource_grab(device_t dev, struct sc_info *sc) +{ + sc->regid = PCIR_MAPS; + sc->reg = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->regid, 0, ~0, + ALS_CONFIG_SPACE_BYTES, RF_ACTIVE); + if (sc->reg == 0) { + 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->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, + RF_ACTIVE | RF_SHAREABLE); + if (sc->irq == 0) { + device_printf(dev, "unable to allocate interrupt\n"); + goto bad; + } + + if (bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, als_intr, + sc, &sc->ih)) { + device_printf(dev, "unable to setup interrupt\n"); + goto bad; + } + + if (bus_dma_tag_create(/*parent*/NULL, + /*alignment*/2, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/ALS_BUFFER_SIZE, + /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, &sc->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + return 0; + bad: + als_resource_free(dev, sc); + return ENXIO; +} + +static int +als_pci_attach(device_t dev) +{ + struct sc_info *sc; + u_int32_t data; + char status[SND_STATUSLEN]; + + if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + bzero(sc, sizeof(*sc)); + sc->dev = dev; + + 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); + /* + * By default the power to the various components on the + * ALS4000 is entirely controlled by the pci powerstate. We + * could attempt finer grained control by setting GCR6.31. + */ +#if __FreeBSD_version > 500000 + if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { + /* Reset the power state. */ + device_printf(dev, "chip is in D%d power mode " + "-- setting to D0\n", pci_get_powerstate(dev)); + pci_set_powerstate(dev, PCI_POWERSTATE_D0); + } +#else + data = pci_read_config(dev, ALS_PCI_POWERREG, 2); + if ((data & 0x03) != 0) { + device_printf(dev, "chip is in D%d power mode " + "-- setting to D0\n", data & 0x03); + data &= ~0x03; + pci_write_config(dev, ALS_PCI_POWERREG, data, 2); + } +#endif + + if (als_resource_grab(dev, sc)) { + device_printf(dev, "failed to allocate resources\n"); + goto bad_attach; + } + + if (als_init(sc)) { + device_printf(dev, "failed to initialize hardware\n"); + goto bad_attach; + } + + if (mixer_init(dev, &als_mixer_class, sc)) { + device_printf(dev, "failed to initialize mixer\n"); + goto bad_attach; + } + + if (pcm_register(dev, sc, 1, 1)) { + device_printf(dev, "failed to register pcm entries\n"); + goto bad_attach; + } + + pcm_addchan(dev, PCMDIR_PLAY, &alspchan_class, sc); + pcm_addchan(dev, PCMDIR_REC, &alsrchan_class, sc); + + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld", + rman_get_start(sc->reg), rman_get_start(sc->irq)); + pcm_setstatus(dev, status); + return 0; + + bad_attach: + als_resource_free(dev, sc); + free(sc, M_DEVBUF); + return ENXIO; +} + +static int +als_pci_detach(device_t dev) +{ + struct sc_info *sc; + int r; + + r = pcm_unregister(dev); + if (r) + return r; + + sc = pcm_getdevinfo(dev); + als_uninit(sc); + als_resource_free(dev, sc); + free(sc, M_DEVBUF); + return 0; +} + +static int +als_pci_suspend(device_t dev) +{ + struct sc_info *sc = pcm_getdevinfo(dev); + + sc->pch.dma_was_active = als_playback_stop(&sc->pch); + sc->rch.dma_was_active = als_capture_stop(&sc->rch); + als_uninit(sc); + return 0; +} + +static int +als_pci_resume(device_t dev) +{ + struct sc_info *sc = pcm_getdevinfo(dev); + + if (als_init(sc) != 0) { + device_printf(dev, "unable to reinitialize the card\n"); + return ENXIO; + } + + if (mixer_reinit(dev) != 0) { + device_printf(dev, "unable to reinitialize the mixer\n"); + return ENXIO; + } + + if (sc->pch.dma_was_active) { + als_playback_start(&sc->pch); + } + + if (sc->rch.dma_was_active) { + als_capture_start(&sc->rch); + } + return 0; +} + +static device_method_t als_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, als_pci_probe), + DEVMETHOD(device_attach, als_pci_attach), + DEVMETHOD(device_detach, als_pci_detach), + DEVMETHOD(device_suspend, als_pci_suspend), + DEVMETHOD(device_resume, als_pci_resume), + { 0, 0 } +}; + +static driver_t als_driver = { + "pcm", + als_methods, + sizeof(struct snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(snd_als, pci, als_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_als, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_als, 1); diff --git a/sys/dev/sound/pci/als4000.h b/sys/dev/sound/pci/als4000.h new file mode 100644 index 0000000..c9ff8c1 --- /dev/null +++ b/sys/dev/sound/pci/als4000.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2001 Orion Hodson + * 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#define ALS_PCI_ID0 0x40004005 +#define ALS_PCI_POWERREG 0xe0 + +#define ALS_CONFIG_SPACE_BYTES 128 +#define ALS_BUFFER_SIZE 8192 + +#define ALS_GCR_DATA 0x08 +#define ALS_GCR_INDEX 0x0c +# define ALS_GCR_MISC 0x8c +# define ALS_GCR_TEST 0x90 +# define ALS_GCR_DMA0_START 0x91 +# define ALS_GCR_DMA0_MODE 0x92 +# define ALS_GCR_DMA1_START 0x93 +# define ALS_GCR_DMA1_MODE 0x94 +# define ALS_GCR_DMA3_START 0x95 +# define ALS_GCR_DMA3_MODE 0x96 +# define ALS_GCR_DMA_EMULATION 0x99 +# define ALS_GCR_FIFO0_CURRENT 0xa0 +# define ALS_GCR_FIFO0_STATUS 0xa1 +# define ALS_GCR_FIFO1_START 0xa2 +# define ALS_GCR_FIFO1_COUNT 0xa3 +# define ALS_GCR_FIFO1_CURRENT 0xa4 +# define ALS_GCR_FIFO1_STATUS 0xa5 +# define ALS_GCR_POWER 0xa6 +# define ALS_GCR_PIC_ACCESS 0xa7 + +#define ALS_SB_MPU_IRQ 0x0e + +#define ALS_MIXER_DATA 0x15 +#define ALS_MIXER_INDEX 0x14 +# define ALS_SB16_RESET 0x00 +# define ALS_SB16_DMA_SETUP 0x81 +# define ALS_CONTROL 0xc0 +# define ALS_SB16_CONFIG ALS_CONTROL + 0x00 +# define ALS_MISC_CONTROL ALS_CONTROL + 0x02 +# define ALS_FIFO1_LENGTH_LO ALS_CONTROL + 0x1c +# define ALS_FIFO1_LENGTH_HI ALS_CONTROL + 0x1d +# define ALS_FIFO1_CONTROL ALS_CONTROL + 0x1e +# define ALS_FIFO1_STOP 0x00 +# define ALS_FIFO1_RUN 0x80 +# define ALS_FIFO1_PAUSE 0x40 +# define ALS_FIFO1_STEREO 0x20 +# define ALS_FIFO1_SIGNED 0x10 +# define ALS_FIFO1_8BIT 0x04 + +#define ALS_ESP_RST 0x16 +#define ALS_CR1E_ACK_PORT 0x16 + +#define ALS_ESP_RD_DATA 0x1a +#define ALS_ESP_WR_DATA 0x1c +#define ALS_ESP_WR_STATUS 0x1c +#define ALS_ESP_RD_STATUS8 0x1e +#define ALS_ESP_RD_STATUS16 0x1f +# define ALS_ESP_SAMPLE_RATE 0x41 + +#define ALS_MIDI_DATA 0x30 +#define ALS_MIDI_STATUS 0x31 + +/* Interrupt masks */ +#define ALS_IRQ_STATUS8 0x01 +#define ALS_IRQ_STATUS16 0x02 +#define ALS_IRQ_MPUIN 0x04 +#define ALS_IRQ_CR1E 0x20 + +/* Sample Rate Locks */ +#define ALS_RATE_LOCK_PLAYBACK 0x01 +#define ALS_RATE_LOCK_CAPTURE 0x02 +#define ALS_RATE_LOCK 0x03 diff --git a/sys/modules/sound/driver/als4000/Makefile b/sys/modules/sound/driver/als4000/Makefile new file mode 100644 index 0000000..414c139 --- /dev/null +++ b/sys/modules/sound/driver/als4000/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../../dev/sound/pci + +KMOD= snd_als4000 +SRCS= device_if.h bus_if.h isa_if.h pci_if.h +SRCS+= als4000.c + +.include -- cgit v1.1