/* * 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_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | 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_8000_48000 | SNDRV_PCM_RATE_CONTINUOUS, .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, }; 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, }; /* 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 dma; if (substream->pcm == chip->pcm_adb) { runtime->hw = snd_vortex_playback_hw_adb; dma = vortex_getadb(chip); if (dma < 0) return -EBUSY; substream->runtime->private_data = &chip->dma_adb[dma]; chip->dma_adb[dma].substream = substream; } else { runtime->hw = snd_vortex_playback_hw_wt; dma = vortex_getwt(chip); if (dma < 0) return -EBUSY; substream->runtime->private_data = &chip->dma_wt[dma]; chip->dma_wt[dma].substream = substream; } return 0; } /* close callback */ static int snd_vortex_pcm_close(snd_pcm_substream_t * substream) { //vortex_t *chip = snd_pcm_substream_chip(substream); dma_t *dma = (dma_t*)substream->runtime->private_data; // the hardware-specific codes will be here // Dealloc DMA and remove audio route. dma->substream = NULL; dma->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; dma_t *dma = (dma_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("Vortex: pcm page alloc failed!\n"); return err; } printk("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) { vortex_adbdma_setbuffers(chip, dma->dma, (u32)(runtime->dma_addr), params_period_bytes(hw_params), params_periods(hw_params)); //if (dma->nr_ch) vortex_adb_waveroute(chip, dma->dma, 0); //if (params_channels(hw_params)) vortex_adb_waveroute(chip, dma->dma, params_channels(hw_params)); } else { vortex_wtdma_setbuffers(chip, dma->dma, (u32)(runtime->dma_addr), params_period_bytes(hw_params), params_periods(hw_params)); if (dma->nr_ch) vortex_wt_waveroute(chip, dma->dma, 0); if (params_channels(hw_params)) vortex_wt_waveroute(chip, dma->dma, params_channels(hw_params)); } 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); dma_t *dma = (dma_t*)substream->runtime->private_data; // Delete audio routes. if (substream->pcm == chip->pcm_adb) vortex_adb_waveroute(chip, dma->dma, 0); else vortex_wt_waveroute(chip, dma->dma, 0); 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; dma_t *dma = (dma_t*)substream->runtime->private_data; int fmt, i, cvrt; // set up the hardware with the current configuration. fmt = vortex_alsafmt_aspfmt(runtime->format); if (substream->pcm == chip->pcm_adb) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { i = 1; cvrt = SRC_RATIO(runtime->rate); } else { i = 0; cvrt = SRC_RATIO2(runtime->rate); } vortex_adbdma_setmode(chip, dma->dma, 1, i/*direction*/, fmt, 1/*?*/, 0); for (i=0; i<(chip->dma_adb[dma->dma].nr_ch); i++) vortex_src_change_convratio(chip, i+(dma->dma*2), cvrt); vortex_adbdma_setstartbuffer(chip, dma->dma, 0); } else { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) i = 1; else i = 0; vortex_wtdma_setmode(chip, dma->dma, 1, i, fmt, 1, 0); // FIXME: Set rate (if possible). vortex_wtdma_setstartbuffer(chip, dma->dma, 0); } 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); dma_t *dma = (dma_t*)substream->runtime->private_data; switch (cmd) { case SNDRV_PCM_TRIGGER_START: // do something to start the PCM engine printk("vortex: start %d\n", dma->dma); if (substream->pcm == chip->pcm_adb) { dma->fifo_enabled = 1; vortex_adbdma_startfifo(chip, dma->dma); } else { dma->fifo_enabled = 1; vortex_wtdma_startfifo(chip, dma->dma); } break; case SNDRV_PCM_TRIGGER_STOP: // do something to stop the PCM engine printk("vortex: stop %d\n", dma->dma); if (substream->pcm == chip->pcm_adb) { dma->fifo_enabled = 0; vortex_adbdma_stopfifo(chip, dma->dma); } else { dma->fifo_enabled = 0; vortex_wtdma_stopfifo(chip, dma->dma); } break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (substream->pcm == chip->pcm_adb) vortex_adbdma_pausefifo(chip, dma->dma); else vortex_wtdma_pausefifo(chip, dma->dma); break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->pcm == chip->pcm_adb) vortex_adbdma_resumefifo(chip, dma->dma); else vortex_wtdma_resumefifo(chip, dma->dma); 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); snd_pcm_uframes_t current_ptr = 0; dma_t *dma = (dma_t*)substream->runtime->private_data; spin_lock(chip->lock); if (substream->pcm == chip->pcm_adb) current_ptr = vortex_adbdma_getlinearpos(chip, dma->dma); else current_ptr = vortex_wtdma_getlinearpos(chip, dma->dma); //printk("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; } 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; } 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; } /* end */