/* * Vortex PCM ALSA driver. * * Supports ADB and WT DMA. Unfortunately, WT routing is still a * mistery. To discover that, we need to disassemble the windoze * driver too. * * */ #include #include #include #include #include #include "au88x0.h" #define chip_t vortex_t /* hardware definition */ static snd_pcm_hardware_t snd_vortex_playback_hw_adb = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_RESUME| SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW, .rates = SNDRV_PCM_RATE_CONTINUOUS, .rate_min = 5000, .rate_max = 48000, .channels_min = 1, #ifdef CHIP_AU8830 .channels_max = 4, #else .channels_max = 2, #endif .buffer_bytes_max = 0x10000, .period_bytes_min = 0x100, .period_bytes_max = 0x1000, .periods_min = 1, .periods_max = 64, }; #ifndef CHIP_AU8810 static snd_pcm_hardware_t snd_vortex_playback_hw_wt = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS, // SNDRV_PCM_RATE_48000, .rate_min = 8000, .rate_max = 48000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = 0x10000, .period_bytes_min = 0x0400, .period_bytes_max = 0x1000, .periods_min = 1, .periods_max = 64, }; #endif /* open callback */ static int snd_vortex_pcm_open(snd_pcm_substream_t *substream) { chip_t *chip = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; int err; /* Force equal size periods */ if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) return err; /* Force DMA 32 bit alignment */ if ((err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 4)) < 0) return err; if (substream->pcm == chip->pcm_adb) { runtime->hw = snd_vortex_playback_hw_adb; substream->runtime->private_data = NULL; } #ifndef CHIP_AU8810 else { runtime->hw = snd_vortex_playback_hw_wt; substream->runtime->private_data = NULL; } #endif return 0; } /* close callback */ static int snd_vortex_pcm_close(snd_pcm_substream_t *substream) { //vortex_t *chip = snd_pcm_substream_chip(substream); stream_t *stream = (stream_t*)substream->runtime->private_data; // the hardware-specific codes will be here if (stream != NULL) { stream->substream = NULL; stream->nr_ch = 0; } substream->runtime->private_data = NULL; return 0; } /* hw_params callback */ static int snd_vortex_pcm_hw_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params) { chip_t *chip = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; stream_t *stream = (stream_t*)(substream->runtime->private_data); int err; // Alloc buffer memory. err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); if (err < 0) { printk(KERN_ERR "Vortex: pcm page alloc failed!\n"); return err; } printk(KERN_INFO "Vortex: periods %d, period_bytes %d, channels = %d\n", params_periods(hw_params), params_period_bytes(hw_params), params_channels(hw_params)); // Make audio routes and config buffer DMA. if (substream->pcm == chip->pcm_adb) { int dma; /* Dealloc any routes. */ if (stream != NULL) vortex_adb_allocroute(chip, stream->dma, stream->nr_ch, stream->dir); /* Alloc routes. */ dma = vortex_adb_allocroute(chip, -1, params_channels(hw_params), substream->stream); if (dma < 0) return dma; stream = substream->runtime->private_data = &chip->dma_adb[dma]; stream->substream = substream; /* Setup Buffers. */ vortex_adbdma_setbuffers(chip, dma, (u32)(runtime->dma_addr), params_period_bytes(hw_params), params_periods(hw_params)); } #ifndef CHIP_AU8810 else { if (stream != NULL) vortex_wt_allocroute(chip, substream->number, 0); vortex_wt_allocroute(chip, substream->number, params_channels(hw_params)); stream = substream->runtime->private_data = &chip->dma_wt[substream->number]; stream->substream = substream; vortex_wtdma_setbuffers(chip, substream->number, (u32)(runtime->dma_addr), params_period_bytes(hw_params), params_periods(hw_params)); } #endif return 0; } /* hw_free callback */ static int snd_vortex_pcm_hw_free(snd_pcm_substream_t *substream) { chip_t *chip = snd_pcm_substream_chip(substream); stream_t *stream = (stream_t*)(substream->runtime->private_data); // Delete audio routes. if (substream->pcm == chip->pcm_adb) { if (stream != NULL) vortex_adb_allocroute(chip, stream->dma, stream->nr_ch, stream->dir); } #ifndef CHIP_AU8810 else { if (stream != NULL) vortex_wt_allocroute(chip, stream->dma, 0); } #endif substream->runtime->private_data = NULL; return snd_pcm_lib_free_pages(substream); } /* prepare callback */ static int snd_vortex_pcm_prepare(snd_pcm_substream_t *substream) { vortex_t *chip = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; stream_t *stream = (stream_t*)substream->runtime->private_data; int dma = stream->dma, fmt, dir; // set up the hardware with the current configuration. if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) dir = 1; else dir = 0; fmt = vortex_alsafmt_aspfmt(runtime->format); if (substream->pcm == chip->pcm_adb) { vortex_adbdma_setmode(chip, dma, 1, dir, fmt, 0/*?*/, 0); vortex_adbdma_setstartbuffer(chip, dma, 0); vortex_adb_setsrc(chip, dma, runtime->rate, dir); } #ifndef CHIP_AU8810 else { vortex_wtdma_setmode(chip, dma, 1, dir, fmt, 0, 0); // FIXME: Set rate (i guess using vortex_wt_writereg() somehow). vortex_wtdma_setstartbuffer(chip, dma, 0); } #endif return 0; } /* trigger callback */ static int snd_vortex_pcm_trigger(snd_pcm_substream_t *substream, int cmd) { chip_t *chip = snd_pcm_substream_chip(substream); stream_t *stream = (stream_t*)substream->runtime->private_data; int dma = stream->dma; switch (cmd) { case SNDRV_PCM_TRIGGER_START: // do something to start the PCM engine printk(KERN_INFO "vortex: start %d\n", dma); if (substream->pcm == chip->pcm_adb) { stream->fifo_enabled = 1; vortex_adbdma_startfifo(chip, dma); } #ifndef CHIP_AU8810 else { stream->fifo_enabled = 1; vortex_wtdma_startfifo(chip, dma); } #endif break; case SNDRV_PCM_TRIGGER_STOP: // do something to stop the PCM engine printk(KERN_INFO "vortex: stop %d\n", dma); if (substream->pcm == chip->pcm_adb) { stream->fifo_enabled = 0; vortex_adbdma_stopfifo(chip, dma); } #ifndef CHIP_AU8810 else { stream->fifo_enabled = 0; vortex_wtdma_stopfifo(chip, dma); } #endif break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: printk(KERN_INFO "vortex: pause %d\n", dma); if (substream->pcm == chip->pcm_adb) vortex_adbdma_pausefifo(chip, dma); #ifndef CHIP_AU8810 else vortex_wtdma_pausefifo(chip, dma); #endif break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: printk(KERN_INFO "vortex: resume %d\n", dma); if (substream->pcm == chip->pcm_adb) vortex_adbdma_resumefifo(chip, dma); #ifndef CHIP_AU8810 else vortex_wtdma_resumefifo(chip, dma); #endif break; default: return -EINVAL; } return 0; } /* pointer callback */ static snd_pcm_uframes_t snd_vortex_pcm_pointer(snd_pcm_substream_t *substream) { vortex_t *chip = snd_pcm_substream_chip(substream); stream_t *stream = (stream_t*)substream->runtime->private_data; int dma = stream->dma; snd_pcm_uframes_t current_ptr = 0; spin_lock(&chip->lock); if (substream->pcm == chip->pcm_adb) current_ptr = vortex_adbdma_getlinearpos(chip, dma); #ifndef CHIP_AU8810 else current_ptr = vortex_wtdma_getlinearpos(chip, dma); #endif //printk(KERN_INFO "vortex: pointer = 0x%x\n", current_ptr); spin_unlock(&chip->lock); return (bytes_to_frames(substream->runtime, current_ptr)); } /* operators */ static snd_pcm_ops_t snd_vortex_playback_ops = { .open = snd_vortex_pcm_open, .close = snd_vortex_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_vortex_pcm_hw_params, .hw_free = snd_vortex_pcm_hw_free, .prepare = snd_vortex_pcm_prepare, .trigger = snd_vortex_pcm_trigger, .pointer = snd_vortex_pcm_pointer, //.page = snd_pcm_sgbuf_ops_page, }; /* * definitions of capture are omitted here... */ /* create a pcm device */ int __devinit snd_vortex_new_pcm(vortex_t *chip, int idx, int nr) { snd_pcm_t *pcm; int err; if ((chip == 0)||(idx < 0)||(idx>1)) return -ENODEV; if (idx == 0) { if ((err = snd_pcm_new(chip->card, "Aureal Vortex ADB", idx, nr, nr, &pcm)) < 0) return err; strcpy(pcm->name, "adb"); chip->pcm_adb = pcm; } #ifndef CHIP_AU8810 else { if ((err = snd_pcm_new(chip->card, "Aureal Vortex WT", idx, nr, 0, &pcm)) < 0) return err; strcpy(pcm->name, "wt"); chip->pcm_wt = pcm; } #endif pcm->private_data = chip; /* set operators */ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_vortex_playback_ops); if (idx == 0) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_vortex_playback_ops); /* pre-allocation of buffers */ snd_pcm_lib_preallocate_pci_pages_for_all(chip->pci_dev, pcm, 0x10000, 0x10000); return 0; }