linux/sound/pci/au88x0/au88x0_pcm.c
Takashi Iwai 58a8738cfc ALSA: au88x0: Fix overlapped PCM pointer
au88x0 hardware seems returning the current pointer at the buffer
boundary instead of going back to zero.  This results in spewing
warnings from PCM core.

This patch corrects the return value from the pointer callback within
the proper value range, just returning zero if the position is equal
or above the buffer size.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-04-26 09:55:42 +02:00

705 lines
20 KiB
C

/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* Vortex PCM ALSA driver.
*
* Supports ADB and WT DMA. Unfortunately, WT channels do not run yet.
* It remains stuck,and DMA transfers do not happen.
*/
#include <sound/asoundef.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "au88x0.h"
#define VORTEX_PCM_TYPE(x) (x->name[40])
/* hardware definition */
static struct snd_pcm_hardware 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,
.channels_max = 2,
.buffer_bytes_max = 0x10000,
.period_bytes_min = 0x20,
.period_bytes_max = 0x1000,
.periods_min = 2,
.periods_max = 1024,
};
#ifndef CHIP_AU8820
static struct snd_pcm_hardware snd_vortex_playback_hw_a3d = {
.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,
.channels_max = 1,
.buffer_bytes_max = 0x10000,
.period_bytes_min = 0x100,
.period_bytes_max = 0x1000,
.periods_min = 2,
.periods_max = 64,
};
#endif
static struct snd_pcm_hardware snd_vortex_playback_hw_spdif = {
.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_IEC958_SUBFRAME_LE | SNDRV_PCM_FMTBIT_MU_LAW |
SNDRV_PCM_FMTBIT_A_LAW,
.rates =
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
.rate_min = 32000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = 0x10000,
.period_bytes_min = 0x100,
.period_bytes_max = 0x1000,
.periods_min = 2,
.periods_max = 64,
};
#ifndef CHIP_AU8810
static struct snd_pcm_hardware 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 = 2,
.periods_max = 64,
};
#endif
#ifdef CHIP_AU8830
static unsigned int au8830_channels[3] = {
1, 2, 4,
};
static struct snd_pcm_hw_constraint_list hw_constraints_au8830_channels = {
.count = ARRAY_SIZE(au8830_channels),
.list = au8830_channels,
.mask = 0,
};
#endif
static void vortex_notify_pcm_vol_change(struct snd_card *card,
struct snd_kcontrol *kctl, int activate)
{
if (activate)
kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
else
kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE |
SNDRV_CTL_EVENT_MASK_INFO, &(kctl->id));
}
/* open callback */
static int snd_vortex_pcm_open(struct snd_pcm_substream *substream)
{
vortex_t *vortex = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *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;
/* Avoid PAGE_SIZE boundary to fall inside of a period. */
if ((err =
snd_pcm_hw_constraint_pow2(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES)) < 0)
return err;
snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 64);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
#ifndef CHIP_AU8820
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_A3D) {
runtime->hw = snd_vortex_playback_hw_a3d;
}
#endif
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_SPDIF) {
runtime->hw = snd_vortex_playback_hw_spdif;
switch (vortex->spdif_sr) {
case 32000:
runtime->hw.rates = SNDRV_PCM_RATE_32000;
break;
case 44100:
runtime->hw.rates = SNDRV_PCM_RATE_44100;
break;
case 48000:
runtime->hw.rates = SNDRV_PCM_RATE_48000;
break;
}
}
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB
|| VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_I2S)
runtime->hw = snd_vortex_playback_hw_adb;
#ifdef CHIP_AU8830
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
VORTEX_IS_QUAD(vortex) &&
VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB) {
runtime->hw.channels_max = 4;
snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS,
&hw_constraints_au8830_channels);
}
#endif
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(struct snd_pcm_substream *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(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
vortex_t *chip = snd_pcm_substream_chip(substream);
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) {
dev_err(chip->card->dev, "Vortex: pcm page alloc failed!\n");
return err;
}
/*
pr_info( "Vortex: periods %d, period_bytes %d, channels = %d\n", params_periods(hw_params),
params_period_bytes(hw_params), params_channels(hw_params));
*/
spin_lock_irq(&chip->lock);
// Make audio routes and config buffer DMA.
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
int dma, type = VORTEX_PCM_TYPE(substream->pcm);
/* Dealloc any routes. */
if (stream != NULL)
vortex_adb_allocroute(chip, stream->dma,
stream->nr_ch, stream->dir,
stream->type,
substream->number);
/* Alloc routes. */
dma =
vortex_adb_allocroute(chip, -1,
params_channels(hw_params),
substream->stream, type,
substream->number);
if (dma < 0) {
spin_unlock_irq(&chip->lock);
return dma;
}
stream = substream->runtime->private_data = &chip->dma_adb[dma];
stream->substream = substream;
/* Setup Buffers. */
vortex_adbdma_setbuffers(chip, dma,
params_period_bytes(hw_params),
params_periods(hw_params));
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB) {
chip->pcm_vol[substream->number].active = 1;
vortex_notify_pcm_vol_change(chip->card,
chip->pcm_vol[substream->number].kctl, 1);
}
}
#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->dma = substream->number;
stream->substream = substream;
vortex_wtdma_setbuffers(chip, substream->number,
params_period_bytes(hw_params),
params_periods(hw_params));
}
#endif
spin_unlock_irq(&chip->lock);
return 0;
}
/* hw_free callback */
static int snd_vortex_pcm_hw_free(struct snd_pcm_substream *substream)
{
vortex_t *chip = snd_pcm_substream_chip(substream);
stream_t *stream = (stream_t *) (substream->runtime->private_data);
spin_lock_irq(&chip->lock);
// Delete audio routes.
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
if (stream != NULL) {
if (VORTEX_PCM_TYPE(substream->pcm) == VORTEX_PCM_ADB) {
chip->pcm_vol[substream->number].active = 0;
vortex_notify_pcm_vol_change(chip->card,
chip->pcm_vol[substream->number].kctl,
0);
}
vortex_adb_allocroute(chip, stream->dma,
stream->nr_ch, stream->dir,
stream->type,
substream->number);
}
}
#ifndef CHIP_AU8810
else {
if (stream != NULL)
vortex_wt_allocroute(chip, stream->dma, 0);
}
#endif
substream->runtime->private_data = NULL;
spin_unlock_irq(&chip->lock);
return snd_pcm_lib_free_pages(substream);
}
/* prepare callback */
static int snd_vortex_pcm_prepare(struct snd_pcm_substream *substream)
{
vortex_t *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *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, chip);
spin_lock_irq(&chip->lock);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
vortex_adbdma_setmode(chip, dma, 1, dir, fmt,
runtime->channels == 1 ? 0 : 1, 0);
vortex_adbdma_setstartbuffer(chip, dma, 0);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_SPDIF)
vortex_adb_setsrc(chip, dma, runtime->rate, dir);
}
#ifndef CHIP_AU8810
else {
vortex_wtdma_setmode(chip, dma, 1, fmt, 0, 0);
// FIXME: Set rate (i guess using vortex_wt_writereg() somehow).
vortex_wtdma_setstartbuffer(chip, dma, 0);
}
#endif
spin_unlock_irq(&chip->lock);
return 0;
}
/* trigger callback */
static int snd_vortex_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
vortex_t *chip = snd_pcm_substream_chip(substream);
stream_t *stream = (stream_t *) substream->runtime->private_data;
int dma = stream->dma;
spin_lock(&chip->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
// do something to start the PCM engine
//printk(KERN_INFO "vortex: start %d\n", dma);
stream->fifo_enabled = 1;
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT) {
vortex_adbdma_resetup(chip, dma);
vortex_adbdma_startfifo(chip, dma);
}
#ifndef CHIP_AU8810
else {
dev_info(chip->card->dev, "wt start %d\n", dma);
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);
stream->fifo_enabled = 0;
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
vortex_adbdma_stopfifo(chip, dma);
#ifndef CHIP_AU8810
else {
dev_info(chip->card->dev, "wt stop %d\n", dma);
vortex_wtdma_stopfifo(chip, dma);
}
#endif
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
//printk(KERN_INFO "vortex: pause %d\n", dma);
if (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
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 (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
vortex_adbdma_resumefifo(chip, dma);
#ifndef CHIP_AU8810
else
vortex_wtdma_resumefifo(chip, dma);
#endif
break;
default:
spin_unlock(&chip->lock);
return -EINVAL;
}
spin_unlock(&chip->lock);
return 0;
}
/* pointer callback */
static snd_pcm_uframes_t snd_vortex_pcm_pointer(struct snd_pcm_substream *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 (VORTEX_PCM_TYPE(substream->pcm) != VORTEX_PCM_WT)
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);
current_ptr = bytes_to_frames(substream->runtime, current_ptr);
if (current_ptr >= substream->runtime->buffer_size)
current_ptr = 0;
return current_ptr;
}
/* operators */
static struct snd_pcm_ops 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...
*/
static char *vortex_pcm_prettyname[VORTEX_PCM_LAST] = {
CARD_NAME " ADB",
CARD_NAME " SPDIF",
CARD_NAME " A3D",
CARD_NAME " WT",
CARD_NAME " I2S",
};
static char *vortex_pcm_name[VORTEX_PCM_LAST] = {
"adb",
"spdif",
"a3d",
"wt",
"i2s",
};
/* SPDIF kcontrol */
static int snd_vortex_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int snd_vortex_spdif_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.iec958.status[0] = 0xff;
ucontrol->value.iec958.status[1] = 0xff;
ucontrol->value.iec958.status[2] = 0xff;
ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS;
return 0;
}
static int snd_vortex_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
ucontrol->value.iec958.status[0] = 0x00;
ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL|IEC958_AES1_CON_DIGDIGCONV_ID;
ucontrol->value.iec958.status[2] = 0x00;
switch (vortex->spdif_sr) {
case 32000: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_32000; break;
case 44100: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_44100; break;
case 48000: ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS_48000; break;
}
return 0;
}
static int snd_vortex_spdif_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
int spdif_sr = 48000;
switch (ucontrol->value.iec958.status[3] & IEC958_AES3_CON_FS) {
case IEC958_AES3_CON_FS_32000: spdif_sr = 32000; break;
case IEC958_AES3_CON_FS_44100: spdif_sr = 44100; break;
case IEC958_AES3_CON_FS_48000: spdif_sr = 48000; break;
}
if (spdif_sr == vortex->spdif_sr)
return 0;
vortex->spdif_sr = spdif_sr;
vortex_spdif_init(vortex, vortex->spdif_sr, 1);
return 1;
}
/* spdif controls */
static struct snd_kcontrol_new snd_vortex_mixer_spdif[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.info = snd_vortex_spdif_info,
.get = snd_vortex_spdif_get,
.put = snd_vortex_spdif_put,
},
{
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
.info = snd_vortex_spdif_info,
.get = snd_vortex_spdif_mask_get
},
};
/* subdevice PCM Volume control */
static int snd_vortex_pcm_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = (VORTEX_IS_QUAD(vortex) ? 4 : 2);
uinfo->value.integer.min = -128;
uinfo->value.integer.max = 32;
return 0;
}
static int snd_vortex_pcm_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
int subdev = kcontrol->id.subdevice;
struct pcm_vol *p = &vortex->pcm_vol[subdev];
int max_chn = (VORTEX_IS_QUAD(vortex) ? 4 : 2);
for (i = 0; i < max_chn; i++)
ucontrol->value.integer.value[i] = p->vol[i];
return 0;
}
static int snd_vortex_pcm_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int i;
int changed = 0;
int mixin;
unsigned char vol;
vortex_t *vortex = snd_kcontrol_chip(kcontrol);
int subdev = kcontrol->id.subdevice;
struct pcm_vol *p = &vortex->pcm_vol[subdev];
int max_chn = (VORTEX_IS_QUAD(vortex) ? 4 : 2);
for (i = 0; i < max_chn; i++) {
if (p->vol[i] != ucontrol->value.integer.value[i]) {
p->vol[i] = ucontrol->value.integer.value[i];
if (p->active) {
switch (vortex->dma_adb[p->dma].nr_ch) {
case 1:
mixin = p->mixin[0];
break;
case 2:
default:
mixin = p->mixin[(i < 2) ? i : (i - 2)];
break;
case 4:
mixin = p->mixin[i];
break;
}
vol = p->vol[i];
vortex_mix_setinputvolumebyte(vortex,
vortex->mixplayb[i], mixin, vol);
}
changed = 1;
}
}
return changed;
}
static const DECLARE_TLV_DB_MINMAX(vortex_pcm_vol_db_scale, -9600, 2400);
static struct snd_kcontrol_new snd_vortex_pcm_vol = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "PCM Playback Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ |
SNDRV_CTL_ELEM_ACCESS_INACTIVE,
.info = snd_vortex_pcm_vol_info,
.get = snd_vortex_pcm_vol_get,
.put = snd_vortex_pcm_vol_put,
.tlv = { .p = vortex_pcm_vol_db_scale },
};
/* create a pcm device */
static int snd_vortex_new_pcm(vortex_t *chip, int idx, int nr)
{
struct snd_pcm *pcm;
struct snd_kcontrol *kctl;
int i;
int err, nr_capt;
if (!chip || idx < 0 || idx >= VORTEX_PCM_LAST)
return -ENODEV;
/* idx indicates which kind of PCM device. ADB, SPDIF, I2S and A3D share the
* same dma engine. WT uses it own separate dma engine which can't capture. */
if (idx == VORTEX_PCM_ADB)
nr_capt = nr;
else
nr_capt = 0;
err = snd_pcm_new(chip->card, vortex_pcm_prettyname[idx], idx, nr,
nr_capt, &pcm);
if (err < 0)
return err;
snprintf(pcm->name, sizeof(pcm->name),
"%s %s", CARD_NAME_SHORT, vortex_pcm_name[idx]);
chip->pcm[idx] = pcm;
// This is an evil hack, but it saves a lot of duplicated code.
VORTEX_PCM_TYPE(pcm) = idx;
pcm->private_data = chip;
/* set operators */
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_vortex_playback_ops);
if (idx == VORTEX_PCM_ADB)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
&snd_vortex_playback_ops);
/* pre-allocation of Scatter-Gather buffers */
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
snd_dma_pci_data(chip->pci_dev),
0x10000, 0x10000);
switch (VORTEX_PCM_TYPE(pcm)) {
case VORTEX_PCM_ADB:
err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
snd_pcm_std_chmaps,
VORTEX_IS_QUAD(chip) ? 4 : 2,
0, NULL);
if (err < 0)
return err;
err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_CAPTURE,
snd_pcm_std_chmaps, 2, 0, NULL);
if (err < 0)
return err;
break;
#ifdef CHIP_AU8830
case VORTEX_PCM_A3D:
err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
snd_pcm_std_chmaps, 1, 0, NULL);
if (err < 0)
return err;
break;
#endif
}
if (VORTEX_PCM_TYPE(pcm) == VORTEX_PCM_SPDIF) {
for (i = 0; i < ARRAY_SIZE(snd_vortex_mixer_spdif); i++) {
kctl = snd_ctl_new1(&snd_vortex_mixer_spdif[i], chip);
if (!kctl)
return -ENOMEM;
if ((err = snd_ctl_add(chip->card, kctl)) < 0)
return err;
}
}
if (VORTEX_PCM_TYPE(pcm) == VORTEX_PCM_ADB) {
for (i = 0; i < NR_PCM; i++) {
chip->pcm_vol[i].active = 0;
chip->pcm_vol[i].dma = -1;
kctl = snd_ctl_new1(&snd_vortex_pcm_vol, chip);
if (!kctl)
return -ENOMEM;
chip->pcm_vol[i].kctl = kctl;
kctl->id.device = 0;
kctl->id.subdevice = i;
err = snd_ctl_add(chip->card, kctl);
if (err < 0)
return err;
}
}
return 0;
}